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