001package jmri.jmrit.beantable; 002 003import java.awt.Color; 004import java.awt.event.ActionEvent; 005import java.awt.event.ActionListener; 006 007import javax.annotation.Nonnull; 008import javax.swing.*; 009 010import jmri.InstanceManager; 011import jmri.Manager; 012import jmri.Sensor; 013import jmri.SensorManager; 014import jmri.swing.ManagerComboBox; 015import jmri.swing.SystemNameValidator; 016import jmri.jmrit.beantable.sensor.SensorTableDataModel; 017import jmri.util.JmriJFrame; 018import jmri.util.swing.TriStateJCheckBox; 019 020import org.slf4j.Logger; 021import org.slf4j.LoggerFactory; 022 023/** 024 * Swing action to create and register a SensorTable GUI. 025 * 026 * @author Bob Jacobsen Copyright (C) 2003, 2009 027 */ 028public class SensorTableAction extends AbstractTableAction<Sensor> { 029 030 /** 031 * Create an action with a specific title. 032 * <p> 033 * Note that the argument is the Action title, not the title of the 034 * resulting frame. Perhaps this should be changed? 035 * 036 * @param actionName title of the action 037 */ 038 public SensorTableAction(String actionName) { 039 super(actionName); 040 041 // disable ourself if there is no primary sensor manager available 042 if (sensorManager == null) { 043 super.setEnabled(false); 044 } 045 } 046 047 public SensorTableAction() { 048 this(Bundle.getMessage("TitleSensorTable")); 049 } 050 051 protected SensorManager sensorManager = InstanceManager.getDefault(SensorManager.class); 052 053 /** 054 * {@inheritDoc} 055 */ 056 @Override 057 public void setManager(@Nonnull Manager<Sensor> s) { 058 if (s instanceof SensorManager) { 059 log.debug("setting manager of ST Action{} to {}",this,s.getClass()); 060 sensorManager = (SensorManager) s; 061 if (m != null) { 062 m.setManager(sensorManager); 063 } 064 } 065 } 066 067 /** 068 * Create the JTable DataModel, along with the changes for the specific case 069 * of Sensors. 070 */ 071 @Override 072 protected void createModel() { 073 m = new jmri.jmrit.beantable.sensor.SensorTableDataModel(sensorManager); 074 } 075 076 /** 077 * {@inheritDoc} 078 */ 079 @Override 080 protected void setTitle() { 081 f.setTitle(Bundle.getMessage("TitleSensorTable")); 082 } 083 084 /** 085 * {@inheritDoc} 086 */ 087 @Override 088 protected String helpTarget() { 089 return "package.jmri.jmrit.beantable.SensorTable"; 090 } 091 092 JmriJFrame addFrame = null; 093 094 JTextField hardwareAddressTextField = new JTextField(20); 095 // initially allow any 20 char string, updated by prefixBox selection 096 JTextField userNameField = new JTextField(40); 097 ManagerComboBox<Sensor> prefixBox = new ManagerComboBox<>(); 098 SpinnerNumberModel rangeSpinner = new SpinnerNumberModel(1, 1, 100, 1); // maximum 100 items 099 JSpinner numberToAddSpinner = new JSpinner(rangeSpinner); 100 JCheckBox rangeBox = new JCheckBox(Bundle.getMessage("AddRangeBox")); 101 JLabel hwAddressLabel = new JLabel(Bundle.getMessage("LabelHardwareAddress")); 102 JLabel userNameLabel = new JLabel(Bundle.getMessage("LabelUserName")); 103 String systemSelectionCombo = this.getClass().getName() + ".SystemSelected"; 104 JButton addButton; 105 JLabel statusBarLabel = new JLabel(Bundle.getMessage("HardwareAddStatusEnter"), JLabel.LEADING); 106 jmri.UserPreferencesManager p; 107 Manager<Sensor> connectionChoice = null; 108 SystemNameValidator hardwareAddressValidator; 109 110 /** 111 * {@inheritDoc} 112 */ 113 @Override 114 protected void addPressed(ActionEvent e) { 115 p = InstanceManager.getDefault(jmri.UserPreferencesManager.class); 116 117 if (addFrame == null) { 118 addFrame = new JmriJFrame(Bundle.getMessage("TitleAddSensor")); 119 addFrame.addHelpMenu("package.jmri.jmrit.beantable.SensorAddEdit", true); 120 addFrame.getContentPane().setLayout(new BoxLayout(addFrame.getContentPane(), BoxLayout.Y_AXIS)); 121 122 ActionListener createListener = this::createPressed; 123 ActionListener cancelListener = this::cancelPressed; 124 ActionListener rangeListener = this::canAddRange; 125 configureManagerComboBox(prefixBox, sensorManager, SensorManager.class); 126 userNameField.setName("userName"); // NOI18N 127 prefixBox.setName("prefixBox"); // NOI18N 128 addButton = new JButton(Bundle.getMessage("ButtonCreate")); 129 addButton.addActionListener(createListener); 130 131 log.debug("add frame hwAddValidator is {} prefix box is {}",hardwareAddressValidator, prefixBox.getSelectedItem()); 132 if (hardwareAddressValidator==null){ 133 hardwareAddressValidator = new SystemNameValidator(hardwareAddressTextField, prefixBox.getSelectedItem(), true); 134 } else { 135 hardwareAddressValidator.setManager(prefixBox.getSelectedItem()); 136 } 137 138 // create panel 139 addFrame.add(new AddNewHardwareDevicePanel(hardwareAddressTextField, hardwareAddressValidator, userNameField, prefixBox, 140 numberToAddSpinner, rangeBox, addButton, cancelListener, rangeListener, statusBarLabel)); 141 // tooltip for hwAddressTextField will be assigned later by canAddRange() 142 canAddRange(null); 143 144 addFrame.setEscapeKeyClosesWindow(true); 145 addFrame.getRootPane().setDefaultButton(addButton); 146 147 } 148 hardwareAddressTextField.setName("hwAddressTextField"); // for GUI test NOI18N 149 addButton.setName("createButton"); // for GUI test NOI18N 150 // reset statusBarLabel text 151 statusBarLabel.setText(Bundle.getMessage("HardwareAddStatusEnter")); 152 statusBarLabel.setForeground(Color.gray); 153 154 addFrame.pack(); 155 addFrame.setVisible(true); 156 } 157 158 void cancelPressed(ActionEvent e) { 159 removePrefixBoxListener(prefixBox); 160 addFrame.setVisible(false); 161 addFrame.dispose(); 162 addFrame = null; 163 } 164 165 /** 166 * Respond to Create new item button pressed on Add Sensor pane. 167 * 168 * @param e the click event 169 */ 170 void createPressed(ActionEvent e) { 171 172 int numberOfSensors = 1; 173 174 if (rangeBox.isSelected()) { 175 numberOfSensors = (Integer) numberToAddSpinner.getValue(); 176 } 177 if (numberOfSensors >= 65) { // number beyond which to warn and ask permission; limited by JSpinnerModel to 100 178 if (JOptionPane.showConfirmDialog(addFrame, 179 Bundle.getMessage("WarnExcessBeans", Bundle.getMessage("Sensors"), numberOfSensors), 180 Bundle.getMessage("WarningTitle"), 181 JOptionPane.YES_NO_OPTION) == 1) { 182 return; 183 } 184 } 185 String sensorPrefix = prefixBox.getSelectedItem().getSystemPrefix(); 186 String sName; 187 String uName = userNameField.getText(); 188 String curAddress = hardwareAddressTextField.getText(); 189 190 // initial check for empty entry 191 if (curAddress.length() < 1) { 192 statusBarLabel.setText(Bundle.getMessage("WarningEmptyHardwareAddress")); 193 statusBarLabel.setForeground(Color.red); 194 hardwareAddressTextField.setBackground(Color.red); 195 return; 196 } else { 197 hardwareAddressTextField.setBackground(Color.white); 198 } 199 200 // Add some entry pattern checking, before assembling sName and handing it to the SensorManager 201 StringBuilder statusMessage = new StringBuilder(Bundle.getMessage("ItemCreateFeedback", Bundle.getMessage("BeanNameSensor"))); 202 203 // Compose the first proposed system name from parts: 204 sName = sensorPrefix + InstanceManager.getDefault(SensorManager.class).typeLetter() + curAddress; 205 206 for (int x = 0; x < numberOfSensors; x++) { 207 log.debug("b4 next valid addr for prefix {} system name {} conn choice mgr {}",sensorPrefix,sName, connectionChoice); 208 209 // create the sensor 210 Sensor s; 211 try { 212 s = InstanceManager.getDefault(SensorManager.class).provideSensor(sName); 213 } catch (IllegalArgumentException ex) { 214 // user input no good 215 handleCreateException(ex, sName); 216 return; // return without creating 217 } 218 219 // handle setting user name 220 if (!uName.isEmpty()) { 221 if (InstanceManager.getDefault(SensorManager.class).getByUserName(uName) == null) { 222 s.setUserName(uName); 223 } else { 224 InstanceManager.getDefault(jmri.UserPreferencesManager.class). 225 showErrorMessage(Bundle.getMessage("ErrorTitle"), 226 Bundle.getMessage("ErrorDuplicateUserName", uName), 227 getClassName(), "duplicateUserName", false, true); 228 } 229 } 230 231 // add first and last names to statusMessage uName feedback string 232 // only mention first and last of rangeBox added 233 if (x == 0 || x == numberOfSensors - 1) { 234 statusMessage.append(" ").append(sName).append(" (").append(uName).append(")"); 235 } 236 if (x == numberOfSensors - 2) { 237 statusMessage.append(" ").append(Bundle.getMessage("ItemCreateUpTo")).append(" "); 238 } 239 240 // except on last pass 241 if (x < numberOfSensors-1) { 242 // bump system name 243 try { 244 sName = InstanceManager.getDefault(SensorManager.class).getNextValidSystemName(s); 245 } catch (jmri.JmriException ex) { 246 displayHwError(s.getSystemName(), ex); 247 // directly add to statusBarLabel (but never called?) 248 statusBarLabel.setText(Bundle.getMessage("ErrorConvertHW", sName)); 249 statusBarLabel.setForeground(Color.red); 250 return; 251 } 252 253 // bump user name 254 if (!uName.isEmpty()) { 255 uName = nextName(uName); 256 } 257 } 258 // end of for loop creating rangeBox of Sensors 259 } 260 261 // provide success feedback to user 262 statusBarLabel.setText(statusMessage.toString()); 263 statusBarLabel.setForeground(Color.gray); 264 265 p.setComboBoxLastSelection(systemSelectionCombo, prefixBox.getSelectedItem().getMemo().getUserName()); 266 removePrefixBoxListener(prefixBox); 267 addFrame.setVisible(false); 268 addFrame.dispose(); 269 addFrame = null; 270 } 271 272 private String addEntryToolTip; 273 274 /** 275 * Activate Add a rangeBox option if manager accepts adding more than 1 276 * Sensor and set a manager specific tooltip on the AddNewHardwareDevice 277 * pane. 278 */ 279 private void canAddRange(ActionEvent e) { 280 rangeBox.setEnabled(false); 281 rangeBox.setSelected(false); 282 if (prefixBox.getSelectedIndex() == -1) { 283 prefixBox.setSelectedIndex(0); 284 } 285 connectionChoice = prefixBox.getSelectedItem(); // store in Field for CheckedTextField 286 String systemPrefix = connectionChoice.getSystemPrefix(); 287 rangeBox.setEnabled(((SensorManager) connectionChoice).allowMultipleAdditions(systemPrefix)); 288 addEntryToolTip = connectionChoice.getEntryToolTip(); 289 // show hwAddressTextField field tooltip in the Add Sensor pane that matches system connection selected from combobox 290 hardwareAddressTextField.setToolTipText( 291 Bundle.getMessage("AddEntryToolTipLine1", 292 connectionChoice.getMemo().getUserName(), 293 Bundle.getMessage("Sensors"), 294 addEntryToolTip)); 295 hardwareAddressValidator.setToolTipText(hardwareAddressTextField.getToolTipText()); 296 hardwareAddressValidator.verify(hardwareAddressTextField); 297 } 298 299 void handleCreateException(Exception ex, String hwAddress) { 300 statusBarLabel.setText(ex.getLocalizedMessage()); 301 String err = Bundle.getMessage("ErrorBeanCreateFailed", 302 InstanceManager.getDefault(SensorManager.class).getBeanTypeHandled(),hwAddress); 303 JOptionPane.showMessageDialog(addFrame, err + "\n" + ex.getLocalizedMessage(), 304 err, JOptionPane.ERROR_MESSAGE); 305 } 306 307 protected void setDefaultDebounce(JFrame _who) { 308 SpinnerNumberModel activeSpinnerModel = new SpinnerNumberModel((Long)sensorManager.getDefaultSensorDebounceGoingActive(), (Long)0L, Sensor.MAX_DEBOUNCE, (Long)1L); // MAX_DEBOUNCE is a Long; casts are to force needed signature 309 JSpinner activeSpinner = new JSpinner(activeSpinnerModel); 310 activeSpinner.setPreferredSize(new JTextField(Long.toString(Sensor.MAX_DEBOUNCE).length()+1).getPreferredSize()); 311 SpinnerNumberModel inActiveSpinnerModel = new SpinnerNumberModel((Long)sensorManager.getDefaultSensorDebounceGoingInActive(), (Long)0L, Sensor.MAX_DEBOUNCE, (Long)1L); // MAX_DEBOUNCE is a Long; casts are to force needed signature 312 JSpinner inActiveSpinner = new JSpinner(inActiveSpinnerModel); 313 inActiveSpinner.setPreferredSize(new JTextField(Long.toString(Sensor.MAX_DEBOUNCE).length()+1).getPreferredSize()); 314 315 JPanel input = new JPanel(); // panel to hold formatted input for dialog 316 input.setLayout(new BoxLayout(input, BoxLayout.Y_AXIS)); 317 318 JTextArea message = new JTextArea(Bundle.getMessage("SensorGlobalDebounceMessageBox")); // multi line 319 message.setEditable(false); 320 message.setOpaque(false); 321 input.add(message); 322 323 JPanel active = new JPanel(); 324 active.add(new JLabel(Bundle.getMessage("SensorActiveTimer"))); 325 active.add(activeSpinner); 326 input.add(active); 327 328 JPanel inActive = new JPanel(); 329 inActive.add(new JLabel(Bundle.getMessage("SensorInactiveTimer"))); 330 inActive.add(inActiveSpinner); 331 input.add(inActive); 332 333 int retval = JOptionPane.showOptionDialog(_who, 334 input, Bundle.getMessage("SensorGlobalDebounceMessageTitle"), 335 0, JOptionPane.INFORMATION_MESSAGE, null, 336 new Object[]{Bundle.getMessage("ButtonOK"), Bundle.getMessage("ButtonCancel")}, null); 337 log.debug("dialog retval={}", retval); 338 if (retval != 0) { 339 return; 340 } 341 342 // Allow the sensor manager to handle checking if the values have changed 343 sensorManager.setDefaultSensorDebounceGoingActive((Long) activeSpinner.getValue()); 344 sensorManager.setDefaultSensorDebounceGoingInActive((Long) inActiveSpinner.getValue()); 345 m.fireTableDataChanged(); 346 } 347 348 protected void setDefaultState(JFrame _who) { 349 String[] sensorStates = new String[]{Bundle.getMessage("BeanStateUnknown"), Bundle.getMessage("SensorStateInactive"), Bundle.getMessage("SensorStateActive"), Bundle.getMessage("BeanStateInconsistent")}; 350 JComboBox<String> stateCombo = new JComboBox<>(sensorStates); 351 switch (jmri.jmrix.internal.InternalSensorManager.getDefaultStateForNewSensors()) { 352 case jmri.Sensor.ACTIVE: 353 stateCombo.setSelectedItem(Bundle.getMessage("SensorStateActive")); 354 break; 355 case jmri.Sensor.INACTIVE: 356 stateCombo.setSelectedItem(Bundle.getMessage("SensorStateInactive")); 357 break; 358 case jmri.Sensor.INCONSISTENT: 359 stateCombo.setSelectedItem(Bundle.getMessage("BeanStateInconsistent")); 360 break; 361 default: 362 stateCombo.setSelectedItem(Bundle.getMessage("BeanStateUnknown")); 363 } 364 365 JPanel input = new JPanel(); // panel to hold formatted input for dialog 366 input.add(new JLabel(Bundle.getMessage("SensorInitialStateMessageBox"))); 367 JPanel stateBoxPane = new JPanel(); 368 stateBoxPane.add(stateCombo); 369 input.add(stateBoxPane); 370 371 int retval = JOptionPane.showOptionDialog(_who, 372 input, Bundle.getMessage("InitialSensorState"), 373 0, JOptionPane.INFORMATION_MESSAGE, null, 374 new Object[]{Bundle.getMessage("ButtonOK"), Bundle.getMessage("ButtonCancel")}, null); 375 if (retval != 0) { 376 return; 377 } 378 int defaultState = jmri.Sensor.UNKNOWN; 379 String selectedState = (String) stateCombo.getSelectedItem(); 380 if (selectedState.equals(Bundle.getMessage("SensorStateActive"))) { 381 defaultState = jmri.Sensor.ACTIVE; 382 } else if (selectedState.equals(Bundle.getMessage("SensorStateInactive"))) { 383 defaultState = jmri.Sensor.INACTIVE; 384 } else if (selectedState.equals(Bundle.getMessage("BeanStateInconsistent"))) { 385 defaultState = jmri.Sensor.INCONSISTENT; 386 } 387 388 jmri.jmrix.internal.InternalSensorManager.setDefaultStateForNewSensors(defaultState); 389 } 390 391 /** 392 * Insert a table specific Defaults menu. Account for the Window and Help 393 * menus, which are already added to the menu bar as part of the creation of 394 * the JFrame, by adding the Tools menu 2 places earlier unless the table is 395 * part of the ListedTableFrame, that adds the Help menu later on. 396 * 397 * @param f the JFrame of this table 398 */ 399 @Override 400 public void setMenuBar(BeanTableFrame<Sensor> f) { 401 final jmri.util.JmriJFrame finalF = f; // needed for anonymous ActionListener class 402 JMenuBar menuBar = f.getJMenuBar(); 403 // check for menu 404 boolean menuAbsent = true; 405 for (int i = 0; i < menuBar.getMenuCount(); ++i) { 406 String name = menuBar.getMenu(i).getAccessibleContext().getAccessibleName(); 407 if (name.equals(Bundle.getMessage("MenuDefaults"))) { 408 // using first menu for check, should be identical to next JMenu Bundle 409 menuAbsent = false; 410 break; 411 } 412 } 413 if (menuAbsent) { // create it 414 JMenu optionsMenu = new JMenu(Bundle.getMessage("MenuDefaults")); 415 JMenuItem item = new JMenuItem(Bundle.getMessage("GlobalDebounce")); 416 optionsMenu.add(item); 417 item.addActionListener((ActionEvent e) -> { 418 setDefaultDebounce(finalF); 419 }); 420 item = new JMenuItem(Bundle.getMessage("InitialSensorState")); 421 optionsMenu.add(item); 422 item.addActionListener((ActionEvent e) -> { 423 setDefaultState(finalF); 424 }); 425 int pos = menuBar.getMenuCount() - 1; // count the number of menus to insert the TableMenus before 'Window' and 'Help' 426 int offset = 1; 427 log.debug("setMenuBar number of menu items = {}", pos); 428 for (int i = 0; i <= pos; i++) { 429 if (menuBar.getComponent(i) instanceof JMenu) { 430 if (((AbstractButton) menuBar.getComponent(i)).getText().equals(Bundle.getMessage("MenuHelp"))) { 431 offset = -1; // correct for use as part of ListedTableAction where the Help Menu is not yet present 432 } 433 } 434 } 435 menuBar.add(optionsMenu, pos + offset); 436 } 437 } 438 439 @Override 440 protected void configureTable(JTable table){ 441 super.configureTable(table); 442 showDebounceBox.addActionListener((ActionEvent e) -> { ((SensorTableDataModel)m).showDebounce(showDebounceBox.isSelected(), table); }); 443 showPullUpBox.addActionListener((ActionEvent e) -> { ((SensorTableDataModel)m).showPullUp(showPullUpBox.isSelected(), table); }); 444 showStateForgetAndQueryBox.addActionListener((ActionEvent e) -> { ((SensorTableDataModel)m).showStateForgetAndQuery(showStateForgetAndQueryBox.isSelected(), table); }); 445 } 446 447 private final TriStateJCheckBox showDebounceBox = new TriStateJCheckBox(Bundle.getMessage("SensorDebounceCheckBox")); 448 private final TriStateJCheckBox showPullUpBox = new TriStateJCheckBox(Bundle.getMessage("SensorPullUpCheckBox")); 449 private final TriStateJCheckBox showStateForgetAndQueryBox = new TriStateJCheckBox(Bundle.getMessage("ShowStateForgetAndQuery")); 450 451 /** 452 * {@inheritDoc} 453 */ 454 @Override 455 public void addToFrame(BeanTableFrame<Sensor> f) { 456 f.addToBottomBox(showDebounceBox, this.getClass().getName()); 457 showDebounceBox.setToolTipText(Bundle.getMessage("SensorDebounceToolTip")); 458 f.addToBottomBox(showPullUpBox, this.getClass().getName()); 459 showPullUpBox.setToolTipText(Bundle.getMessage("SensorPullUpToolTip")); 460 f.addToBottomBox(showStateForgetAndQueryBox, this.getClass().getName()); 461 showStateForgetAndQueryBox.setToolTipText(Bundle.getMessage("StateForgetAndQueryBoxToolTip")); 462 } 463 464 /** 465 * Override to update showDebounceBox, showPullUpBox, showStateForgetAndQueryBox. 466 * {@inheritDoc} 467 */ 468 @Override 469 protected void columnsVisibleUpdated(boolean[] colsVisible){ 470 log.debug("columns updated {}",colsVisible); 471 showDebounceBox.setState(new boolean[]{ 472 colsVisible[SensorTableDataModel.ACTIVEDELAY], 473 colsVisible[SensorTableDataModel.INACTIVEDELAY], 474 colsVisible[SensorTableDataModel.USEGLOBALDELAY] }); 475 showPullUpBox.setState(new boolean[]{ 476 colsVisible[SensorTableDataModel.PULLUPCOL]}); 477 showStateForgetAndQueryBox.setState(new boolean[]{ 478 colsVisible[SensorTableDataModel.FORGETCOL], 479 colsVisible[SensorTableDataModel.QUERYCOL] }); 480 } 481 482 /** 483 * {@inheritDoc} 484 */ 485 @Override 486 public void addToPanel(AbstractTableTabAction<Sensor> f) { 487 String connectionName = sensorManager.getMemo().getUserName(); 488 489 if (sensorManager.getClass().getName().contains("ProxySensorManager")) { 490 connectionName = "All"; 491 } 492 f.addToBottomBox(showDebounceBox, connectionName); 493 showDebounceBox.setToolTipText(Bundle.getMessage("SensorDebounceToolTip")); 494 f.addToBottomBox(showPullUpBox, connectionName); 495 showPullUpBox.setToolTipText(Bundle.getMessage("SensorPullUpToolTip")); 496 f.addToBottomBox(showStateForgetAndQueryBox, connectionName); 497 showStateForgetAndQueryBox.setToolTipText(Bundle.getMessage("StateForgetAndQueryBoxToolTip")); 498 } 499 500 /** 501 * {@inheritDoc} 502 */ 503 @Override 504 public void setMessagePreferencesDetails() { 505 InstanceManager.getDefault(jmri.UserPreferencesManager.class).setPreferenceItemDetails(getClassName(), "duplicateUserName", Bundle.getMessage("DuplicateUserNameWarn")); 506 super.setMessagePreferencesDetails(); 507 } 508 509 /** 510 * {@inheritDoc} 511 */ 512 @Override 513 protected String getClassName() { 514 return SensorTableAction.class.getName(); 515 } 516 517 /** 518 * {@inheritDoc} 519 */ 520 @Override 521 public String getClassDescription() { 522 return Bundle.getMessage("TitleSensorTable"); 523 } 524 525 private final static Logger log = LoggerFactory.getLogger(SensorTableAction.class); 526 527}