001package jmri.jmrix.loconet.duplexgroup.swing; 002 003import java.awt.Cursor; 004import java.awt.Dimension; 005import java.time.LocalDateTime; 006import java.time.format.DateTimeFormatter; 007 008import javax.swing.BoxLayout; 009import javax.swing.JButton; 010import javax.swing.JLabel; 011import javax.swing.JPanel; 012import javax.swing.JScrollPane; 013import javax.swing.JSeparator; 014import javax.swing.JTable; 015import javax.swing.table.DefaultTableModel; 016 017import jmri.jmrix.loconet.*; 018import jmri.jmrix.loconet.duplexgroup.LnDplxGrpInfoImplConstants; 019import jmri.util.swing.ValidatedTextField; 020 021/** 022 * Provides a JPanel for querying and configuring Digitrax Duplex and WiFi 023 * network identification. Provides useful function if one or more devices 024 * are connected to LocoNet. 025 * <p> 026 * This tool makes use of LocoNet messages which have not been publicly 027 * documented by Digitrax. This tool is made possible by the reverse- 028 * engineering efforts of B. Milhaupt. Because these messages have been 029 * reverse-engineered, it is possible that the tool may not function as desired 030 * for some Digitrax hardware, and that future Digitrax hardware may not be 031 * compatible with this tool. 032 * 033 * @author B. Milhaupt Copyright 2010, 2011 034 */ 035public class DuplexGroupInfoPanel extends jmri.jmrix.loconet.swing.LnPanel 036 implements java.beans.PropertyChangeListener { 037 038 // member declarations 039 JButton swingReadButton; 040 JButton swingSetButton; 041 JButton swingUpdateGrpName; 042 JButton swingUpdateChannel; 043 JButton swingUpdatePassword; 044 JButton swingUpdateGroupId; 045 ValidatedTextField swingNameValueField = new ValidatedTextField(1, false, "a", "b"); 046 ValidatedTextField swingChannelValueField = new ValidatedTextField(1, false, "a", "b"); 047 ValidatedTextField swingPasswordValueField = new ValidatedTextField(1, false, "a", "b"); 048 ValidatedTextField swingIdValueField = new ValidatedTextField(1, false, "a", "b"); 049 JTable swingDeviceTable=new JTable(); 050 JTable swingDeviceResponseTable=new JTable(); 051 ResponsesTableModel dtModel; 052 ResponsesTableModel dtrtModel; 053 DateTimeFormatter dtf = DateTimeFormatter.ofPattern("HH:mm:ss.S"); 054 JLabel swingNumUr92Label; 055 JLabel swingStatusValueLabel; 056 private int numDuplexTypeDevices; 057 058 private LnDplxGrpInfoImpl duplexGroupImplementation; 059 060 private int minWindowWidth = 0; 061 062 public DuplexGroupInfoPanel() { 063 swingNameValueField = new ValidatedTextField(9, false, "^.{1,8}$", // NOI18N 064 "ErrorBadGroupName"); 065 066 swingChannelValueField = new ValidatedTextField(3, false, 11, 26, "ErrorBadGroupChannel"); 067 swingPasswordValueField = new ValidatedTextField(5, true, "^[0-9A-C]{4}$", // NOI18N 068 "ErrorBadGroupPassword"); 069 swingIdValueField = new ValidatedTextField(3, false, 0, 127, "ErrorBadGroupId"); 070 swingNameValueField.addPropertyChangeListener(ValidatedTextField.VTF_PC_STAT_LN_UPDATE, this); 071 swingChannelValueField.addPropertyChangeListener(ValidatedTextField.VTF_PC_STAT_LN_UPDATE, this); 072 swingPasswordValueField.addPropertyChangeListener(ValidatedTextField.VTF_PC_STAT_LN_UPDATE, this); 073 swingIdValueField.addPropertyChangeListener(ValidatedTextField.VTF_PC_STAT_LN_UPDATE, this); 074 075 duplexGroupImplementation = null; 076 077 } 078 079 @Override 080 public void initComponents() { 081 // uses swing operations 082 JLabel swingTempLabel; 083 084 try { 085 minWindowWidth = Integer.parseInt(Bundle.getMessage("MinimumWidthForWindow"), 10); 086 } catch (RuntimeException e) { 087 minWindowWidth = DEFAULT_WINDOW_WIDTH; 088 } 089 090 numDuplexTypeDevices = 0; // assume 0 UR92 devices available 091 swingStatusValueLabel = new JLabel(); 092 swingStatusValueLabel.setName("ProcessingInitialStatusMessage"); //this string is used as a reference to a .properties file entry; internationalization is handled there. 093 swingStatusValueLabel.setText(convertToHtml(swingStatusValueLabel.getName(), minWindowWidth)); 094 095 swingNameValueField.setText(Bundle.getMessage("ValueUnknownGroupName")); 096 swingNameValueField.setToolTipText(Bundle.getMessage("ToolTipGroupName")); 097 swingNameValueField.setLastQueriedValue(Bundle.getMessage("ValueUnknownGroupName")); 098 swingUpdateGrpName = new JButton(Bundle.getMessage("ButtonUpdate")); 099 swingUpdateGrpName.setToolTipText(Bundle.getMessage("ButtonUpdateDuplexNameHint")); 100 101 swingChannelValueField.setText(Bundle.getMessage("ValueUnknownGroupChannel")); 102 swingChannelValueField.setToolTipText(Bundle.getMessage("ToolTipGroupChannel")); 103 swingChannelValueField.setLastQueriedValue(Bundle.getMessage("ValueUnknownGroupChannel")); 104 swingUpdateChannel = new JButton(Bundle.getMessage("ButtonUpdate")); 105 swingUpdateChannel.setToolTipText(Bundle.getMessage("ButtonUpdateChannelHint")); 106 107 swingPasswordValueField.setText(Bundle.getMessage("ValueUnknownGroupPassword")); 108 swingPasswordValueField.setToolTipText(Bundle.getMessage("ToolTipGroupPassword")); 109 swingPasswordValueField.setLastQueriedValue(Bundle.getMessage("ValueUnknownGroupPassword")); 110 swingUpdatePassword = new JButton(Bundle.getMessage("ButtonUpdate")); 111 swingUpdatePassword.setToolTipText(Bundle.getMessage("ButtonDuplexPasswordHint")); 112 113 swingIdValueField.setText(Bundle.getMessage("ValueUnknownGroupID")); 114 swingIdValueField.setToolTipText(Bundle.getMessage("ToolTipGroupID")); 115 swingIdValueField.setLastQueriedValue(Bundle.getMessage("ValueUnknownGroupID")); 116 swingUpdateGroupId = new JButton(Bundle.getMessage("ButtonUpdate")); 117 swingUpdateGroupId.setToolTipText(Bundle.getMessage("ButtonDuplexGroupIDHint")); 118 119 // Want to force space in the GUI for N lines of status, where N comes 120 // from a value in the DuplexGroup.properties file; 121 // assume 2 if not able to parse the variable from the .properties file. 122 int numLinesForStatus = 2; 123 try { 124 numLinesForStatus = Integer.parseInt(Bundle.getMessage("FixedLinesForStatus")); 125 } catch (RuntimeException e) { 126 numLinesForStatus = 2; 127 } 128 129 swingStatusValueLabel.setPreferredSize(new java.awt.Dimension(minWindowWidth, 130 numLinesForStatus * (int) swingStatusValueLabel.getMaximumSize().getHeight())); 131 132 swingReadButton = new JButton(Bundle.getMessage("ButtonRead")); 133 swingSetButton = new JButton(Bundle.getMessage("ButtonSet")); 134 135 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); // need to override the default layout of FlowLayout (horizontal layout) 136 swingReadButton.hasFocus(); 137 138 JPanel swingTempPanel = new JPanel(); 139 swingTempPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 5, 3)); 140 swingTempPanel.add(swingTempLabel = new JLabel(Bundle.getMessage("LabelDuplexName"))); 141 swingTempPanel.add(swingNameValueField); 142 swingTempLabel.setLabelFor(swingNameValueField); // for "assistive technology" per JLabel on-line documentation 143 swingTempPanel.add(swingUpdateGrpName); 144 add(swingTempPanel); 145 146 swingTempPanel = new JPanel(); 147 swingTempPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 5, 3)); 148 swingTempPanel.add(swingTempLabel = new JLabel(Bundle.getMessage("LabelDuplexChannel"))); 149 swingTempPanel.add(swingChannelValueField); 150 swingTempLabel.setLabelFor(swingChannelValueField); // for "assistive technology" per JLabel on-line documentation 151 swingTempPanel.add(swingUpdateChannel); 152 add(swingTempPanel); 153 154 swingTempPanel = new JPanel(); 155 swingTempPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 5, 3)); 156 swingTempPanel.add(swingTempLabel = new JLabel(Bundle.getMessage("LabelDuplexPassword"))); 157 swingTempPanel.add(swingPasswordValueField); 158 swingTempLabel.setLabelFor(swingPasswordValueField); // for "assistive technology" per JLabel on-line documentation 159 swingTempPanel.add(swingUpdatePassword); 160 add(swingTempPanel); 161 162 swingTempPanel = new JPanel(); 163 swingTempPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 5, 3)); 164 swingTempPanel.add(swingTempLabel = new JLabel(Bundle.getMessage("LabelDuplexGroupID"))); 165 swingTempPanel.add(swingIdValueField); 166 swingTempLabel.setLabelFor(swingIdValueField); // for "assistive technology" per JLabel on-line documentation 167 swingTempPanel.add(swingUpdateGroupId); 168 add(swingTempPanel); 169 170 swingTempPanel = new JPanel(); 171 swingTempPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 5, 3)); 172 swingTempPanel.add(swingNumUr92Label = new JLabel(" ")); 173 updateDisplayOfUr92Count(); 174 add(swingTempPanel); 175 176 swingTempPanel = new JPanel(); 177 swingTempPanel.setLayout(new java.awt.BorderLayout()); 178 dtModel = new ResponsesTableModel( 179 new String[]{ Bundle.getMessage("IPLDeviceInfoHeaderType"), 180 Bundle.getMessage("IPLDeviceInfoHeaderSerial"), 181 Bundle.getMessage("IPLDeviceInfoHeaderFwv") },0); 182 swingDeviceTable.setModel(dtModel); 183 JScrollPane sp=new JScrollPane(swingDeviceTable); 184 sp.setPreferredSize(new Dimension(0,swingDeviceTable.getRowHeight()*5)); //five rows high 185 swingTempPanel.add(sp); 186 add(swingTempPanel); 187 188 swingTempPanel = new JPanel(); 189 swingTempPanel.setLayout(new java.awt.BorderLayout()); 190 dtrtModel = new ResponsesTableModel(new String[]{ Bundle.getMessage("IPLDeviceRespHeaderTime"), 191 Bundle.getMessage("IPLDeviceRespHeaderName"), 192 Bundle.getMessage("IPLDeviceRespHeaderChannel"), 193 Bundle.getMessage("IPLDeviceRespHeaderPassword"), 194 Bundle.getMessage("IPLDeviceRespHeaderGroupID") },0); 195 swingDeviceResponseTable.setModel(dtrtModel); 196 JScrollPane spdtrt=new JScrollPane(swingDeviceResponseTable); 197 spdtrt.setPreferredSize(new Dimension(0,swingDeviceResponseTable.getRowHeight()*5)); //five rows high 198 swingTempPanel.add(spdtrt); 199 add(swingTempPanel); 200 201 swingTempPanel = new JPanel(); 202 swingTempPanel.setLayout(new java.awt.FlowLayout()); 203 swingTempPanel.add(swingReadButton); 204 swingTempPanel.add(swingSetButton); 205 add(swingTempPanel); 206 207 add(new JSeparator()); 208 209 swingTempPanel = new JPanel(); 210 swingTempPanel.setLayout(new java.awt.FlowLayout()); 211 swingTempPanel.add(swingStatusValueLabel); 212 add(swingTempPanel); 213 214 swingUpdateGrpName.addActionListener(e -> { 215 swingNameValueField.setForeground(COLOR_OK); 216 updateStatusLineMessage(" ", COLOR_STATUS_OK); 217 if (validateGroupNameField() == false) { 218 swingNameValueField.setForeground(COLOR_ERROR_VAL); 219 updateStatusLineMessage("ErrorBadGroupName", COLOR_STATUS_ERROR); 220 swingNameValueField.requestFocusInWindow(); 221 return; 222 } 223 writeGroupName(); 224 readButtonActionPerformed(); 225 }); 226 swingUpdateChannel.addActionListener(e -> { 227 swingChannelValueField.setForeground(COLOR_OK); 228 updateStatusLineMessage(" ", COLOR_STATUS_OK); 229 if (validateGroupNameField() == false) { 230 swingChannelValueField.setForeground(COLOR_ERROR_VAL); 231 updateStatusLineMessage("ErrorBadGroupName", COLOR_STATUS_ERROR); 232 swingChannelValueField.requestFocusInWindow(); 233 return; 234 } 235 writeChannel(); 236 readButtonActionPerformed(); 237 }); 238 swingUpdatePassword.addActionListener(e -> { 239 swingPasswordValueField.setForeground(COLOR_OK); 240 updateStatusLineMessage(" ", COLOR_STATUS_OK); 241 if (validateGroupNameField() == false) { 242 swingPasswordValueField.setForeground(COLOR_ERROR_VAL); 243 updateStatusLineMessage("ErrorBadGroupName", COLOR_STATUS_ERROR); 244 swingPasswordValueField.requestFocusInWindow(); 245 return; 246 } 247 writePassword(); 248 readButtonActionPerformed(); 249 }); 250 swingUpdateGroupId.addActionListener(e -> { 251 swingIdValueField.setForeground(COLOR_OK); 252 updateStatusLineMessage(" ", COLOR_STATUS_OK); 253 if (validateGroupNameField() == false) { 254 swingIdValueField.setForeground(COLOR_ERROR_VAL); 255 updateStatusLineMessage("ErrorBadGroupName", COLOR_STATUS_ERROR); 256 swingIdValueField.requestFocusInWindow(); 257 return; 258 } 259 writeDuplexId(); 260 readButtonActionPerformed(); 261 }); 262 263 swingSetButton.addActionListener(new java.awt.event.ActionListener() { 264 @Override 265 public void actionPerformed(java.awt.event.ActionEvent e) { 266 setButtonActionPerformed(); 267 } 268 }); 269 swingReadButton.addActionListener(new java.awt.event.ActionListener() { 270 @Override 271 public void actionPerformed(java.awt.event.ActionEvent e) { 272 scanButtonActionPerformed(); 273 } 274 275 }); 276 } 277 278 @Override 279 public String getHelpTarget() { 280 return "package.jmri.jmrix.loconet.duplexgroup.DuplexGroupTabbedPanel"; // NOI18N 281 } 282 283 @Override 284 public String getTitle() { 285 return Bundle.getMessage("Title"); 286 } 287 288 @Override 289 public void initComponents(LocoNetSystemConnectionMemo memo) { 290 super.initComponents(memo); 291 duplexGroupImplementation = new LnDplxGrpInfoImpl(memo); 292 duplexGroupImplementation.addPropertyChangeListener(this); 293 294 scanButtonActionPerformed(); // begin a query for UR92s 295 } 296 297 /** 298 * Update GUI status then generates LocoNet message used to count the 299 * available UR92 devices. 300 */ 301 private void scanButtonActionPerformed() { 302 numDuplexTypeDevices = 0; 303 updateStatusLineMessage("ProcessingFindingUR92s", COLOR_STATUS_OK); 304 duplexGroupImplementation.countUr92sAndQueryDuplexIdentityInfo(); 305 } 306 307 /** 308 * Modify GUI to show that displayed Duplex network information is not 309 * currently valid. Creates and sends LocoNet traffic to query any available 310 * UR92(s) for Duplex network identity information. 311 */ 312 private void readButtonActionPerformed() { 313 if (numDuplexTypeDevices == 0) { 314 scanButtonActionPerformed(); 315 return; 316 } 317 318 swingNameValueField.setForeground(COLOR_OK); // set foreground to default color 319 swingChannelValueField.setForeground(COLOR_OK); // set foreground to default color 320 swingPasswordValueField.setForeground(COLOR_OK); // set foreground to default color 321 swingIdValueField.setForeground(COLOR_OK); // set foreground to default color 322 swingNameValueField.setText(Bundle.getMessage("ValueUnknownGroupName")); 323 swingChannelValueField.setText(Bundle.getMessage("ValueUnknownGroupChannel")); 324 swingPasswordValueField.setText(Bundle.getMessage("ValueUnknownGroupPassword")); 325 swingIdValueField.setText(Bundle.getMessage("ValueUnknownGroupID")); 326 updateStatusLineMessage("ProcessingReadingInfo", COLOR_STATUS_OK); 327 duplexGroupImplementation.queryDuplexGroupIdentity(); 328 updateStatusLineMessage("ProcessingWaitingForReport", COLOR_STATUS_OK); 329 } 330 331 /** 332 * Validate the Duplex group name currently specified in the GUI. If the 333 * group name is invalid, the GUI status line is updated with an appropriate 334 * message. 335 * 336 * @return true if current swingNameValueField is a valid Duplex group name 337 */ 338 private boolean validateGroupNameField() { 339 return swingNameValueField.isValid(); 340 } 341 342 /** 343 * Validates the Duplex group channel number currently specified in the GUI. 344 * If the group channel number is invalid, the GUI status line is updated 345 * with an appropriate message. 346 * 347 * @return true if current swingChannelValueField is a valid Duplex group 348 * channel 349 */ 350 private boolean validateGroupChannelField() { 351 return swingChannelValueField.isValid(); 352 } 353 354 /** 355 * Validate the Duplex group ID number currently specified in the GUI. If 356 * the group ID number is invalid, the GUI status line is updated with an 357 * appropriate message. 358 * 359 * @return true if current swingIdValueField is a valid Duplex group ID 360 * number 361 */ 362 private boolean validateGroupIDField() { 363 return swingIdValueField.isValid(); 364 } 365 366 /** 367 * Validate the Duplex group password currently specified in the GUI. 368 * 369 * @return true if current swingNameValueField is a valid Duplex group 370 * password 371 */ 372 private boolean validateGroupPasswordField() { 373 return swingPasswordValueField.isValid(); 374 } 375 376 /** 377 * Perform actions required when the Set Group Information button is 378 * clicked. 379 * <p> 380 * First validates the Duplex group name, channel, password, and group ID. 381 * If any is invalid, the GUI status line is updated and the process is 382 * aborted. If all are valid, the appropriate LocoNet messages are created, 383 * and sent in sequence, to update the attached UR92(s) to the specified 384 * Duplex group identity information, then initiates a read of the UR92(s) 385 * to update the GUI. 386 */ 387 private void setButtonActionPerformed() { 388 boolean result = true; 389 390 // assume all values are valid, so put fields to valid color and clear 391 // status line 392 swingNameValueField.setForeground(COLOR_OK); 393 swingChannelValueField.setForeground(COLOR_OK); 394 swingPasswordValueField.setForeground(COLOR_OK); 395 swingIdValueField.setForeground(COLOR_OK); 396 updateStatusLineMessage(" ", COLOR_STATUS_OK); 397 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); 398 399 if (validateGroupNameField() == false) { 400 swingNameValueField.setForeground(COLOR_ERROR_VAL); 401 result = false; 402 updateStatusLineMessage("ErrorBadGroupName", COLOR_STATUS_ERROR); 403 swingNameValueField.requestFocusInWindow(); 404 } else if (validateGroupChannelField() == false) { 405 swingChannelValueField.setForeground(COLOR_ERROR_VAL); 406 result = false; 407 updateStatusLineMessage("ErrorBadGroupChannel", COLOR_STATUS_ERROR); 408 swingChannelValueField.requestFocusInWindow(); 409 } else if (validateGroupPasswordField() == false) { 410 swingPasswordValueField.setForeground(COLOR_ERROR_VAL); 411 result = false; 412 updateStatusLineMessage("ErrorBadGroupPassword", COLOR_STATUS_ERROR); 413 swingPasswordValueField.requestFocusInWindow(); 414 } else if (validateGroupIDField() == false) { 415 swingIdValueField.setForeground(COLOR_ERROR_VAL); 416 result = false; 417 updateStatusLineMessage("ErrorBadGroupId", COLOR_STATUS_ERROR); 418 swingIdValueField.requestFocusInWindow(); 419 } 420 421 if (result == true) { 422 if (!writeGroupName() || 423 !writeChannel() || 424 !writePassword() || 425 !writeDuplexId()) { 426 // do nothing 427 } else { 428 readButtonActionPerformed(); 429 } 430 } 431 setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 432 } 433 434 // delay need between messages slower Duplex/LNWI devices 435 // eg LNWI, UR93 436 private static final int WRITE_DELAY = 250; 437 private static final int WRITE_DELAY_PASSWORD = 2500; // LNWI need a lot of time on password updates 438 439 // four routines for performing the writes. 440 private boolean writeGroupName() { 441 updateStatusLineMessage("ProcessingGroupUpdate", COLOR_STATUS_OK); 442 StringBuilder writeGroupName = new StringBuilder(); 443 writeGroupName.append(swingNameValueField.getText()); 444 writeGroupName.append(" "); // ensure length at least 8 characters 445 writeGroupName.setLength(LnDplxGrpInfoImplConstants.DPLX_NAME_LEN); // trim to required length 446 try { 447 duplexGroupImplementation.setDuplexGroupName(writeGroupName.toString()); 448 Thread.sleep(WRITE_DELAY); 449 } catch (LocoNetException | InterruptedException e) { 450 // illegal Duplex Group Name 451 updateStatusLineMessage("ErrorBadGroupName", COLOR_STATUS_ERROR); 452 swingNameValueField.requestFocusInWindow(); 453 return false; 454 } 455 return true; 456 } 457 458 private boolean writeChannel() { 459 updateStatusLineMessage("ProcessingGroupUpdate", COLOR_STATUS_OK); 460 try { 461 duplexGroupImplementation.setDuplexGroupChannel(Integer.parseInt(swingChannelValueField.getText(), 10)); 462 Thread.sleep(WRITE_DELAY); 463 } catch (LocoNetException | InterruptedException e) { 464 // illegal Duplex Group Channel 465 updateStatusLineMessage("ErrorBadGroupChannel", COLOR_STATUS_ERROR); 466 swingChannelValueField.requestFocusInWindow(); 467 return false; 468 } 469 return true; 470 } 471 472 private boolean writePassword() { 473 try { 474 duplexGroupImplementation.setDuplexGroupPassword(swingPasswordValueField.getText()); 475 Thread.sleep(WRITE_DELAY_PASSWORD); 476 } catch (LocoNetException | InterruptedException e) { 477 // illegal Duplex Group Password 478 updateStatusLineMessage("ErrorBadGroupPassword", COLOR_STATUS_ERROR); 479 swingPasswordValueField.requestFocusInWindow(); 480 return false; 481 } 482 return true; 483 } 484 485 private boolean writeDuplexId() { 486 try { 487 duplexGroupImplementation.setDuplexGroupId(swingIdValueField.getText()); 488 Thread.sleep(WRITE_DELAY); 489 } catch (LocoNetException | InterruptedException e) { 490 // illegal Duplex Group Id 491 updateStatusLineMessage("ErrorBadGroupId", COLOR_STATUS_ERROR); 492 swingIdValueField.requestFocusInWindow(); 493 return false; 494 } 495 return true; 496 } 497 498 /** 499 * 500 * @param s Name of tag in .properties file for string to be converted 501 * to HTML 502 * @param width Width of resulting HTML, in Swing dimensional units 503 * @return String containing HTML for input string s 504 */ 505 private String convertToHtml(String s, int width) { 506 String result = "<html><body><div align=center style='width: "; // NOI18N 507 508 if (s.length() == 1) { 509 result = " "; 510 } else { 511 result = result + width + "'>" + // NOI18N 512 Bundle.getMessage(s); 513 } 514 return result; 515 } 516 517 /** 518 * Update the GUI label showing the number of UR92 devices. 519 */ 520 private void updateDisplayOfUr92Count() { 521 Object[] messageArguments = { 522 numDuplexTypeDevices, 523 numDuplexTypeDevices 524 }; 525 java.text.MessageFormat formatter = new java.text.MessageFormat(""); 526 527 try { 528 formatter.applyPattern(Bundle.getMessage("LabelDeviceCountUR92")); 529 double[] pluralLimits = {0, 1, 2}; 530 String[] devicePlurals = { 531 Bundle.getMessage("LabelDeviceCountUR92Plural0"), 532 Bundle.getMessage("LabelDeviceCountUR92Plural1"), 533 Bundle.getMessage("LabelDeviceCountUR92Plural2") 534 }; 535 java.text.ChoiceFormat pluralForm = new java.text.ChoiceFormat(pluralLimits, devicePlurals); 536 java.text.Format[] messageFormats = { 537 java.text.NumberFormat.getInstance(), 538 pluralForm 539 }; 540 formatter.setFormats(messageFormats); 541 String ur92CountString = formatter.format(messageArguments); 542 swingNumUr92Label.setText(ur92CountString); 543 } catch (RuntimeException e) { 544 swingNumUr92Label.setText(Bundle.getMessage("LabelDeviceCountUR92Except", numDuplexTypeDevices)); 545 // eat the exception and show a simple, gramatically ambiguous message 546 } 547 swingNumUr92Label.repaint(); 548 } 549 550 private void updateStatusLineMessage(String statusMessage, java.awt.Color fgColor) { 551 if (statusMessage == null) { 552 swingStatusValueLabel.setForeground(fgColor); 553 swingStatusValueLabel.setName(" "); //this string is used as a reference to a .properties file entry; internationalization is handled there. 554 swingStatusValueLabel.setText(convertToHtml(swingStatusValueLabel.getName(), minWindowWidth)); 555 } else { 556 swingStatusValueLabel.setForeground(fgColor); 557 swingStatusValueLabel.setName(statusMessage); //this string is used as a reference to a .properties file entry; internationalization is handled there. 558 swingStatusValueLabel.setText(convertToHtml(swingStatusValueLabel.getName(), minWindowWidth)); 559 } 560 } 561 562 /** 563 * Process the "property change" events from LnDplxGrpInfoImpl and 564 * ValidatedTextField object. Includes processing of: 565 * <ul> 566 * <li>ValidatedTextField - ValidatedTextField.VTF_PC_STAT_LN_UPDATE 567 * <li>LnDplxGrpInfoImpl - StatusDontBlastError 568 * <li>StatusLineUpdate 569 * <li>NumberOfUr92sUpdate 570 * </ul> 571 */ 572 @Override 573 public void propertyChange(java.beans.PropertyChangeEvent evt) { 574 // these messages can arrive without a complete 575 // GUI, in which case we just ignore them 576 String eventName = evt.getPropertyName(); 577 578 if (eventName.equals(LnDplxGrpInfoImpl.DPLX_PC_STAT_LN_UPDATE_IF_NOT_CURRENTLY_ERROR)) { 579 if (swingStatusValueLabel == null) { 580 return; 581 } 582 583 if (swingStatusValueLabel.getForeground().equals(COLOR_STATUS_ERROR)) { 584 return; // don't overwrite an existing error message for this case 585 } 586 String statusMessage = (String) evt.getNewValue(); 587 java.awt.Color fgColor = COLOR_STATUS_OK; 588 if (statusMessage == null) { 589 updateStatusLineMessage(" ", COLOR_STATUS_OK); 590 return; 591 } // if current status message begins with Error, then don't replace it. 592 else if ((statusMessage.startsWith("Error")) 593 || (swingStatusValueLabel.getForeground().equals(COLOR_STATUS_ERROR))) { 594 return; 595 } 596 // is not an error message, so replace it. 597 updateStatusLineMessage(statusMessage, fgColor); 598 } else if ( eventName.equals(LnDplxGrpInfoImpl.DPLX_IPL_DEVICE_DETAILS) ) { 599 LnDplxGrpInfoImpl.BasicIPLDeviceInfo tmpItem = (LnDplxGrpInfoImpl.BasicIPLDeviceInfo)evt.getNewValue(); 600 if (tmpItem.getType().isEmpty()) { 601 for (int ix = dtModel.getRowCount()-1 ; ix > -1 ; ix --) { 602 dtModel.removeRow(ix); 603 } 604 } else { 605 dtModel.addRow(new String[] {tmpItem.getType(), tmpItem.getSerialNumber(), tmpItem.getSwVersion()}); 606 } 607 return; 608 } else if ( eventName.equals(LnDplxGrpInfoImpl.DPLX_IPL_DEVICE_RESPONSE_DETAILS) ) { 609 LnDplxGrpInfoImpl.BasicIPLDeviceResponseInfo tmpItem = (LnDplxGrpInfoImpl.BasicIPLDeviceResponseInfo)evt.getNewValue(); 610 if (tmpItem.getGroupName().isEmpty()) { 611 for (int ix = dtrtModel.getRowCount()-1 ; ix > -1 ; ix --) { 612 dtrtModel.removeRow(ix); 613 } 614 } else { 615 dtrtModel.addRow(new String[] {dtf.format(LocalDateTime.now()),tmpItem.getGroupName(), tmpItem.getChannel(), tmpItem.getPassword(), tmpItem.getGroupId()}); 616 } 617 return; 618 } else if ((eventName.equals(ValidatedTextField.VTF_PC_STAT_LN_UPDATE)) 619 || (eventName.equals(LnDplxGrpInfoImpl.DPLX_PC_STAT_LN_UPDATE))) { 620 if (swingStatusValueLabel == null) { 621 return; 622 } 623 String statusMessage = (String) evt.getNewValue(); 624 if (statusMessage == null) { 625 updateStatusLineMessage(" ", COLOR_STATUS_OK); 626 return; 627 } else { 628 java.awt.Color fgColor = COLOR_STATUS_OK; 629 if (statusMessage.startsWith("ERROR:")) { // NOI18N 630 fgColor = COLOR_STATUS_ERROR; 631 statusMessage = statusMessage.substring(6); 632 } else if (statusMessage.startsWith("Error")) { // NOI18N 633 fgColor = COLOR_STATUS_ERROR; 634 } 635 updateStatusLineMessage(statusMessage, fgColor); 636 } 637 } else if (eventName.equals("NumberOfUr92sUpdate")) { // NOI18N 638 numDuplexTypeDevices = (Integer) evt.getNewValue(); 639 updateDisplayOfUr92Count(); 640 } else if (eventName.equals(LnDplxGrpInfoImpl.DPLX_PC_NAME_VALIDITY)) { 641 swingNameValueField.setForeground(COLOR_OK); 642 swingNameValueField.setEnabled(evt.getNewValue().equals(true)); 643 if (swingNameValueField.isEnabled() 644 && swingChannelValueField.isEnabled() 645 && swingPasswordValueField.isEnabled() 646 && swingIdValueField.isEnabled()) { 647 swingSetButton.setEnabled(true); 648 } else { 649 swingSetButton.setEnabled(false); 650 } 651 } else if (eventName.equals(LnDplxGrpInfoImpl.DPLX_PC_CHANNEL_VALIDITY)) { 652 swingChannelValueField.setForeground(COLOR_OK); 653 swingChannelValueField.setEnabled(evt.getNewValue().equals(true)); 654 if (swingNameValueField.isEnabled() 655 && swingChannelValueField.isEnabled() 656 && swingPasswordValueField.isEnabled() 657 && swingIdValueField.isEnabled()) { 658 swingSetButton.setEnabled(true); 659 } else { 660 swingSetButton.setEnabled(false); 661 } 662 } else if (eventName.equals(LnDplxGrpInfoImpl.DPLX_PC_PASSWORD_VALIDITY)) { 663 swingPasswordValueField.setForeground(COLOR_OK); 664 swingPasswordValueField.setEnabled(evt.getNewValue().equals(true)); 665 if (swingNameValueField.isEnabled() 666 && swingChannelValueField.isEnabled() 667 && swingPasswordValueField.isEnabled() 668 && swingIdValueField.isEnabled()) { 669 swingSetButton.setEnabled(true); 670 } else { 671 swingSetButton.setEnabled(false); 672 } 673 } else if (eventName.equals(LnDplxGrpInfoImpl.DPLX_PC_ID_VALIDITY)) { 674 swingIdValueField.setForeground(COLOR_OK); 675 swingIdValueField.setEnabled(evt.getNewValue().equals(true)); 676 if (swingNameValueField.isEnabled() 677 && swingChannelValueField.isEnabled() 678 && swingPasswordValueField.isEnabled() 679 && swingIdValueField.isEnabled()) { 680 swingSetButton.setEnabled(true); 681 } else { 682 swingSetButton.setEnabled(false); 683 } 684 } else if (eventName.equals(LnDplxGrpInfoImpl.DPLX_PC_NAME_UPDATE)) { 685 if (evt.getNewValue().equals(true)) { 686 String s = duplexGroupImplementation.getFetchedDuplexGroupName(); 687 showValidGroupName(s); 688 swingNameValueField.setLastQueriedValue(s); 689 } else { 690 disableGroupName(); 691 } 692 } else if (eventName.equals(LnDplxGrpInfoImpl.DPLX_PC_CHANNEL_UPDATE)) { 693 if (evt.getNewValue().equals(true)) { 694 String s = duplexGroupImplementation.getFetchedDuplexGroupChannel(); 695 showValidGroupChannel(s); 696 swingChannelValueField.setLastQueriedValue(s); 697 } else { 698 disableGroupChannel(); 699 } 700 } else if (eventName.equals(LnDplxGrpInfoImpl.DPLX_PC_PASSWORD_UPDATE)) { 701 if (evt.getNewValue().equals(true)) { 702 String s = duplexGroupImplementation.getFetchedDuplexGroupPassword(); 703 showValidGroupPassword(s); 704 swingPasswordValueField.setLastQueriedValue(s); 705 } else { 706 disableGroupPassword(); 707 } 708 } else if (eventName.equals(LnDplxGrpInfoImpl.DPLX_PC_ID_UPDATE)) { 709 if (evt.getNewValue().equals(true)) { 710 String s = duplexGroupImplementation.getFetchedDuplexGroupId(); 711 showValidGroupId(s); 712 swingIdValueField.setLastQueriedValue(s); 713 } else { 714 disableGroupId(); 715 } 716 } 717 } 718 719 private void showValidGroupName(String gn) { 720 swingNameValueField.setForeground(COLOR_OK); 721 swingNameValueField.setBackground(COLOR_BG_UNEDITED); 722 swingNameValueField.setEnabled(true); 723 swingNameValueField.setText(gn); 724 } 725 726 private void disableGroupName() { 727 swingNameValueField.setForeground(COLOR_OK); 728 swingNameValueField.setBackground(COLOR_BG_UNEDITED); 729 swingNameValueField.setEnabled(false); 730 swingNameValueField.setText("????????"); 731 } 732 733 private void showValidGroupChannel(String gc) { 734 swingChannelValueField.setForeground(COLOR_OK); 735 swingChannelValueField.setBackground(COLOR_BG_UNEDITED); 736 swingChannelValueField.setEnabled(true); 737 swingChannelValueField.setText(gc); 738 } 739 740 private void disableGroupChannel() { 741 swingChannelValueField.setForeground(COLOR_OK); 742 swingChannelValueField.setBackground(COLOR_BG_UNEDITED); 743 swingChannelValueField.setEnabled(false); 744 swingChannelValueField.setText("??"); 745 } 746 747 private void showValidGroupPassword(String gp) { 748 swingPasswordValueField.setForeground(COLOR_OK); 749 swingPasswordValueField.setBackground(COLOR_BG_UNEDITED); 750 swingPasswordValueField.setEnabled(true); 751 swingPasswordValueField.setText(gp); 752 } 753 754 private void disableGroupPassword() { 755 swingPasswordValueField.setForeground(COLOR_OK); 756 swingPasswordValueField.setBackground(COLOR_BG_UNEDITED); 757 swingPasswordValueField.setEnabled(false); 758 swingPasswordValueField.setText("????"); 759 } 760 761 private void showValidGroupId(String gi) { 762 swingIdValueField.setForeground(COLOR_OK); 763 swingIdValueField.setBackground(COLOR_BG_UNEDITED); 764 swingIdValueField.setEnabled(true); 765 swingIdValueField.setText(gi); 766 } 767 768 private void disableGroupId() { 769 swingIdValueField.setForeground(COLOR_OK); 770 swingIdValueField.setBackground(COLOR_BG_UNEDITED); 771 swingIdValueField.setEnabled(false); 772 swingIdValueField.setText("???"); 773 } 774 775 // defines for colorizing the user input GUI elements and status line 776 public final static java.awt.Color COLOR_MISMATCH_VAL = java.awt.Color.red.darker(); 777 public final static java.awt.Color COLOR_UNKN_VAL = java.awt.Color.yellow.brighter(); 778 public final static java.awt.Color COLOR_READ = null; // use default color for the component 779 public final static java.awt.Color COLOR_BG_EDITED = java.awt.Color.orange; // use default color for the component 780 public final static java.awt.Color COLOR_ERROR_VAL = java.awt.Color.black; 781 public final static java.awt.Color COLOR_OK = java.awt.Color.black; 782 public final static java.awt.Color COLOR_BG_OK = java.awt.Color.white; 783 public final static java.awt.Color COLOR_BG_UNEDITED = java.awt.Color.white; 784 public final static java.awt.Color COLOR_STATUS_OK = java.awt.Color.black; 785 public final static java.awt.Color COLOR_STATUS_ERROR = java.awt.Color.red; 786 public final static java.awt.Color COLOR_BG_ERROR = java.awt.Color.red; 787 788 // helper for laying out the GUI 789 public final static int DEFAULT_WINDOW_WIDTH = 200; 790 791 /** 792 * Nested class to create a DuplexGroupInfoPanel using old-style defaults. 793 * This is most useful when adding DuplexGroupInfoPanel as a JMRI Start-up 794 * action. 795 */ 796 static public class Default extends jmri.jmrix.loconet.swing.LnNamedPaneAction { 797 798 public Default() { 799 super(Bundle.getMessage("MenuItemDuplexInfo"), 800 new jmri.util.swing.sdi.JmriJFrameInterface(), 801 DuplexGroupInfoPanel.class.getName(), 802 jmri.InstanceManager.getDefault(LocoNetSystemConnectionMemo.class)); 803 } 804 } 805 806 // make the table model read only 807 static public class ResponsesTableModel extends DefaultTableModel { 808 public ResponsesTableModel(String[] columns, int rows) { 809 super(columns, rows); 810 } 811 @Override 812 public boolean isCellEditable(int row, int column){ 813 return false; 814 } 815 } 816 817 // private final static Logger log = LoggerFactory.getLogger(DuplexGroupInfoPanel.class); 818 819}