001package jmri.jmrix.loconet.bdl16; 002 003import java.util.Collections; 004import java.awt.GridBagConstraints; 005import java.awt.GridBagLayout; 006import javax.swing.BoxLayout; 007import javax.swing.JComboBox; 008import javax.swing.JLabel; 009import javax.swing.JPanel; 010import javax.swing.BorderFactory; 011import javax.swing.SwingConstants; 012import javax.swing.border.Border; 013import javax.swing.border.TitledBorder; 014import jmri.jmrix.loconet.AbstractBoardProgPanel; 015import jmri.jmrix.loconet.LnConstants; 016import jmri.jmrix.loconet.LocoNetMessage; 017import jmri.jmrix.loconet.LocoNetSystemConnectionMemo; 018import org.slf4j.Logger; 019import org.slf4j.LoggerFactory; 020 021/** 022 * Panel displaying and programming a BDL16x configuration. 023 * <p> 024 * The read and write require a sequence of operations, which we handle with a 025 * state variable. 026 * <p> 027 * Programming of the BDL16x is done via configuration messages, so the BDL16x 028 * should not be put into programming mode via the built-in pushbutton while 029 * this tool is in use. 030 * <p> 031 * Some of the message formats used in this class are Copyright Digitrax, Inc. 032 * and used with permission as part of the JMRI project. That permission does 033 * not extend to uses in other software products. If you wish to use this code, 034 * algorithm or these message formats outside of JMRI, please contact Digitrax 035 * Inc for separate permission. 036 * 037 * @author Bob Jacobsen Copyright (C) 2002, 2004, 2007, 2010 038 */ 039public class BDL16Panel extends AbstractBoardProgPanel { 040 041 /** 042 * BDL16x Configuration Tool. 043 * <p> 044 * Use this constructor when the Unit Address is unknown. 045 */ 046 public BDL16Panel() { 047 this(1, false); 048 } 049 050 JComboBox<Integer> addressComboBox; 051 int[] boardNumbers; 052 int origAccessBoardNum = 0; 053 java.util.ArrayList<Integer> boardNumsEntryValue = new java.util.ArrayList<Integer>(); 054 055 @SuppressWarnings("unchecked") // type erasure means can't ask for new JComboBox<String>[48] 056 JComboBox<String> comboBox[] = new JComboBox[48]; 057 058 /** 059 * BDL16x Programming tool. 060 * <p> 061 * Use this constructor when the Unit Address is known. 062 * 063 * @param boardNum integer for the initial Unit Address 064 * @param readOnInit True to trigger automatic read of the board 065 */ 066 public BDL16Panel(int boardNum, boolean readOnInit) { 067 super(boardNum, readOnInit,"BDL16"); 068 setTypeWord(0x71); // configure BDL16x message type 069 origAccessBoardNum = boardNum; 070 boardNumsEntryValue.add(boardNum); 071 } 072 073 /** 074 * Get the URL for the HTML help for this tool. 075 * 076 * @return URL 077 */ 078 @Override 079 public String getHelpTarget() { 080 return "package.jmri.jmrix.loconet.bdl16.BDL16Frame"; // NOI18N 081 } 082 083 /** 084 * Get the name of the tool for use in the title of the window. 085 * 086 * @return String containing text for the title of the window 087 */ 088 @Override 089 public String getTitle() { 090 return getTitle(Bundle.getMessage("MenuItemBDL16Programmer")); 091 } 092 093 /** 094 * Copy from the GUI to the OpSw array. 095 * <p> 096 * Used before write operations start. 097 */ 098 @Override 099 protected void copyToOpsw() { 100 // copy over the display 101 opsw[1] = comboBox[1].getSelectedIndex()==1; 102 opsw[3] = comboBox[3].getSelectedIndex()==1; 103 opsw[5] = comboBox[5].getSelectedIndex()==1; 104 opsw[6] = comboBox[6].getSelectedIndex()==1; 105 opsw[7] = comboBox[7].getSelectedIndex()==1; 106 opsw[9] = comboBox[9].getSelectedIndex()==1; 107 opsw[10] = comboBox[10].getSelectedIndex()==1; 108 opsw[11] = comboBox[11].getSelectedIndex()==1; 109 opsw[12] = comboBox[12].getSelectedIndex()==1; 110 opsw[13] = comboBox[13].getSelectedIndex()==1; 111 opsw[19] = comboBox[19].getSelectedIndex()==1; 112 opsw[25] = comboBox[25].getSelectedIndex()==1; 113 opsw[26] = comboBox[26].getSelectedIndex()==1; 114 opsw[36] = comboBox[36].getSelectedIndex()==1; 115 opsw[39] = comboBox[39].getSelectedIndex()==1; 116 opsw[42] = comboBox[42].getSelectedIndex()==1; 117 118 int index = comboBox[37].getSelectedIndex(); 119 opsw[37] = ((index==1) || (index==3))?true:false; 120 opsw[38] = (index >=2)?true:false; 121 122 index = comboBox[43].getSelectedIndex(); 123 opsw[43] = ((index==1) || (index==3))?true:false; 124 opsw[44] = (index >=2)?true:false; 125 opsw[40] = comboBox[40].getSelectedIndex()==1; 126 } 127 128 /** 129 * Update the GUI elements. 130 */ 131 @Override 132 protected void updateDisplay() { 133 comboBox[1].setSelectedIndex(opsw[1]?1:0); 134 comboBox[3].setSelectedIndex(opsw[3]?1:0); 135 comboBox[5].setSelectedIndex(opsw[5]?1:0); 136 comboBox[6].setSelectedIndex(opsw[6]?1:0); 137 comboBox[7].setSelectedIndex(opsw[7]?1:0); 138 comboBox[9].setSelectedIndex(opsw[9]?1:0); 139 comboBox[10].setSelectedIndex(opsw[10]?1:0); 140 comboBox[11].setSelectedIndex(opsw[11]?1:0); 141 comboBox[12].setSelectedIndex(opsw[12]?1:0); 142 comboBox[13].setSelectedIndex(opsw[13]?1:0); 143 comboBox[19].setSelectedIndex(opsw[19]?1:0); 144 comboBox[25].setSelectedIndex(opsw[25]?1:0); 145 comboBox[26].setSelectedIndex(opsw[26]?1:0); 146 comboBox[36].setSelectedIndex(opsw[36]?1:0); 147 comboBox[39].setSelectedIndex(opsw[39]?1:0); 148 comboBox[42].setSelectedIndex(opsw[42]?1:0); 149 comboBox[40].setSelectedIndex(opsw[40]?1:0); 150 151 int temp = opsw[37]?1:0; 152 temp += opsw[38]?2:0; 153 comboBox[37].setSelectedIndex(temp); 154 155 temp = opsw[43]?1:0; 156 temp += opsw[44]?2:0; 157 comboBox[43].setSelectedIndex(temp); 158 } 159 160 /** 161 * Determine the next OpSw to be accessed. 162 * 163 * @param state most-recently accessed OpSw 164 * @return next OpSw to be accessed 165 */ 166 @Override 167 protected int nextState(int state) { 168 switch (state) { 169 case 1: 170 return 3; 171 case 3: 172 return 5; 173 case 5: 174 return 6; 175 case 6: 176 return 7; 177 case 7: 178 return 9; 179 case 9: 180 return 10; 181 case 10: 182 return 11; 183 case 11: 184 return 12; 185 case 12: 186 return 13; 187 case 13: 188 return 19; 189 case 19: 190 return 25; 191 case 25: 192 return 26; 193 case 26: 194 return 36; 195 case 36: 196 return 37; 197 case 37: 198 return 38; 199 case 38: 200 return 39; 201 case 39: 202 return 42; 203 case 42: 204 return 43; 205 case 43: 206 return 44; 207 case 44: 208 return 40; // have to do 40 last 209 case 40: 210 return 0; // done! 211 default: 212 log.error("unexpected state {}", state); // NOI18N 213 return 0; 214 } 215 } 216 217 /** 218 * Initialize LocoNet connection for use by the tool. 219 * 220 * @param memo the LocoNet Connection 221 */ 222 @Override 223 public void initComponents(LocoNetSystemConnectionMemo memo) { 224 super.initComponents(memo); 225 LocoNetMessage m = new LocoNetMessage(6); 226 m.setElement(0, LnConstants.OPC_MULTI_SENSE); 227 m.setElement(1, 0x62); 228 m.setElement(2, 0); 229 m.setElement(3, 0x70); 230 m.setElement(4, 0); 231 memo.getLnTrafficController().sendLocoNetMessage(m); 232 } 233 234 private GridBagConstraints gc = new GridBagConstraints(); 235 private GridBagLayout gl = new GridBagLayout(); 236 237 /** 238 * Initialize the GUI elements for use by the tool. 239 */ 240 @Override 241 public void initComponents() { 242 JPanel addressingPanel = provideAddressing(" "); // create read/write buttons, address 243 244 245 int indexOfTargetBoardAddress = 0; 246 247 addressComboBox = new JComboBox<>(); 248 for (Integer index = 0; index < boardNumsEntryValue.size(); ++index) { 249 if (boardNumsEntryValue.get(index) == origAccessBoardNum) { 250 origAccessBoardNum = -1; 251 indexOfTargetBoardAddress = index; 252 } 253 addressComboBox.addItem(boardNumsEntryValue.get(index)); 254 } 255 256 addressComboBox.setSelectedIndex(indexOfTargetBoardAddress); 257 addressingPanel.add(addressComboBox, 2); 258 addressingPanel.getComponent(1).setVisible(false); 259 addressComboBox.setEditable(true); 260 261 addressingPanel.add(new JLabel(Bundle.getMessage("LabelBoardID")),1); 262 addressingPanel.getComponent(0).setVisible(false); 263 264 readAllButton.setPreferredSize(null); 265 readAllButton.setText(Bundle.getMessage("ButtonReadFullSheet")); 266 readAllButton.setToolTipText(Bundle.getMessage("ToolTipButtonReadFullSheet")); 267 268 writeAllButton.setPreferredSize(null); 269 writeAllButton.setText(Bundle.getMessage("ButtonWriteFullSheet")); 270 writeAllButton.setToolTipText(Bundle.getMessage("ToolTipButtonWriteFullSheet")); 271 272 // make both buttons a little bit bigger, with identical (preferred) sizes 273 // (width increased because some computers/displays trim the button text) 274 java.awt.Dimension d = writeAllButton.getPreferredSize(); 275 int w = d.width; 276 d = readAllButton.getPreferredSize(); 277 if (d.width > w) { 278 w = d.width; 279 } 280 writeAllButton.setPreferredSize(new java.awt.Dimension((int) (w * 1.1), d.height)); 281 readAllButton.setPreferredSize(new java.awt.Dimension((int) (w * 1.1), d.height)); 282 283 appendLine(addressingPanel); // add read/write buttons, address 284 285 JPanel frame1 = new JPanel(); 286 frame1.setLayout(new BoxLayout(frame1, BoxLayout.PAGE_AXIS)); 287 288 TitledBorder allBoardsTitleBorder; 289 Border blackline; 290 blackline = BorderFactory.createLineBorder(java.awt.Color.black); 291 allBoardsTitleBorder = BorderFactory.createTitledBorder(blackline, 292 Bundle.getMessage("TitledBorderLabelAllBoards")); 293 frame1.setBorder(allBoardsTitleBorder); 294 295 JPanel allBoardsOptions = new JPanel(); 296 allBoardsOptions.setLayout(gl); 297 // configure GridBagConstraints 298 gc.ipadx = 15; 299 gc.ipady = 20; 300 gc.gridy = 0; 301 302 allBoardsOptions.add(getLabel(1)); 303 allBoardsOptions.add(getComboBox(1)); 304 gc.gridy++; 305 306 allBoardsOptions.add(getLabel(9)); 307 allBoardsOptions.add(getComboBox(9)); 308 gc.gridy++; 309 310 allBoardsOptions.add(getLabel(10)); 311 allBoardsOptions.add(getComboBox(10)); 312 gc.gridy++; 313 314 allBoardsOptions.add(getLabel(11)); 315 allBoardsOptions.add(getComboBox(11)); 316 gc.gridy++; 317 318 allBoardsOptions.add(getLabel(12)); 319 allBoardsOptions.add(getComboBox(12)); 320 gc.gridy++; 321 322 allBoardsOptions.add(getLabel(13)); 323 allBoardsOptions.add(getComboBox(13)); 324 gc.gridy++; 325 326 allBoardsOptions.add(getLabel(19)); 327 allBoardsOptions.add(getComboBox(19)); 328 gc.gridy++; 329 330 allBoardsOptions.add(getLabel(25)); 331 allBoardsOptions.add(getComboBox(25)); 332 gc.gridy++; 333 334 allBoardsOptions.add(getLabel(26)); 335 allBoardsOptions.add(getComboBox(26)); 336 gc.gridy++; 337 338 allBoardsOptions.add(getLabel(40)); 339 allBoardsOptions.add(getComboBox(40)); 340 341 frame1.add(allBoardsOptions); 342 343 JPanel frame2 = new JPanel(); 344 frame2.setLayout(new BoxLayout(frame2, BoxLayout.PAGE_AXIS)); 345 TitledBorder bdl162Bdl168BoardsTitleBorder; 346 bdl162Bdl168BoardsTitleBorder = BorderFactory.createTitledBorder(blackline, 347 Bundle.getMessage("TitledBorderLabelBdl162Bdl168Boards")); 348 frame2.setBorder(bdl162Bdl168BoardsTitleBorder); 349 350 JPanel bdl162Bdl168BoardsOptions = new JPanel(); 351 bdl162Bdl168BoardsOptions.setLayout(gl); 352 gc.gridy = 0; 353 354 bdl162Bdl168BoardsOptions.add(getLabel(3)); 355 bdl162Bdl168BoardsOptions.add(getComboBox(3)); 356 gc.gridy++; 357 358 bdl162Bdl168BoardsOptions.add(getLabel(5)); 359 bdl162Bdl168BoardsOptions.add(getComboBox(5)); 360 gc.gridy++; 361 362 bdl162Bdl168BoardsOptions.add(getLabel(6)); 363 bdl162Bdl168BoardsOptions.add(getComboBox(6)); 364 gc.gridy++; 365 366 bdl162Bdl168BoardsOptions.add(getLabel(7)); 367 bdl162Bdl168BoardsOptions.add(getComboBox(7)); 368 gc.gridy++; 369 370 bdl162Bdl168BoardsOptions.add(getLabel(36)); 371 bdl162Bdl168BoardsOptions.add(getComboBox(36)); 372 373 frame2.add(bdl162Bdl168BoardsOptions); 374 frame1.add(frame2); 375 376 JPanel frame3 = new JPanel(); 377 frame3.setLayout(new BoxLayout(frame3, BoxLayout.PAGE_AXIS)); 378 379 TitledBorder bdl168SpecificTitleBorder; 380 bdl168SpecificTitleBorder = BorderFactory.createTitledBorder(blackline, 381 Bundle.getMessage("TitledBorderLabelBdl168Only")); 382 frame3.setBorder(bdl168SpecificTitleBorder); 383 384 JPanel bdl168SpecificOptions = new JPanel(); 385 bdl168SpecificOptions.setLayout(gl); 386 gc.gridy = 0; 387 388 bdl168SpecificOptions.add(getLabel(37, 38)); 389 bdl168SpecificOptions.add(getComboBox(37, 38)); 390 gc.gridy++; 391 392 bdl168SpecificOptions.add(getLabel(42)); 393 bdl168SpecificOptions.add(getComboBox(42)); 394 gc.gridy++; 395 396 bdl168SpecificOptions.add(getLabel(39)); 397 bdl168SpecificOptions.add(getComboBox(39)); 398 gc.gridy++; 399 400 bdl168SpecificOptions.add(getLabel(43, 44)); 401 bdl168SpecificOptions.add(getComboBox(43, 44)); 402 403 frame3.add(bdl168SpecificOptions); 404 frame2.add(frame3); 405 406 appendLine(frame1); 407 appendLine(provideStatusLine()); 408 setStatus(Bundle.getMessage("STATUS_TEXT_BOARD_MODE")); 409 410 panelToScroll(); 411 412 } 413 414 /** 415 * Create a JLabel for an OpSw. 416 * 417 * @param n OpSw number 418 * @return the JPanel into which the Label is placed 419 */ 420 private JLabel getLabel(int n) { 421 String number = Integer.toString(n); 422 if (number.length() == 1) { 423 number = "0" + number; 424 } 425 JLabel label = new JLabel(Bundle.getMessage("LabelX", number)); // NOI18N 426 label.setHorizontalAlignment(SwingConstants.RIGHT); 427 label.setPreferredSize(new JLabel("XXXXXXXXXXXXXXXXXX").getPreferredSize()); // NOI18N 428 // layout 429 gc.gridx = 0; 430 gc.weightx = 0.5; 431 gc.anchor = GridBagConstraints.EAST; 432 gl.setConstraints(label, gc); 433 return label; 434 } 435 436 /** 437 * Create a JLabel for an OpSw. 438 * 439 * @param n first OpSw number 440 * @param n2 second OpSw number 441 * @return the JPanel into which the Label is placed 442 */ 443 private JLabel getLabel(int n, int n2) { 444 String number = Integer.toString(n); 445 if (number.length() == 1) { 446 number = "0" + number; 447 } 448 String number2 = Integer.toString(n2); 449 if (number2.length() == 1) { 450 number2 = "0" + number2; 451 } 452 JLabel label = new JLabel(Bundle.getMessage("LabelXY", number, number2)); // NOI18N 453 label.setHorizontalAlignment(SwingConstants.RIGHT); 454 label.setPreferredSize(new JLabel("XXXXXXXXXXXXXXXXXX").getPreferredSize()); // NOI18N 455 // layout 456 gc.gridx = 0; 457 gc.weightx = 0.5; 458 gc.anchor = GridBagConstraints.EAST; 459 gl.setConstraints(label, gc); 460 return label; 461 } 462 463 /** 464 * Create a JComboBox with two possible values. 465 * <p> 466 * For a given OpSw number, create a JComboBox containing the appropriate 467 * strings from the bundle. Sets the initial value based on the OpSw's 468 * reported default value. 469 * 470 * @param n OpSw number 471 * @return the JPanel into which the JComboBox is placed 472 */ 473 private JComboBox<String> getComboBox(int n) { 474 String number = Integer.toString(n); 475 if (number.length() == 1) { 476 number = "0" + number; 477 } 478 String[] s = new String[] {Bundle.getMessage("COMBOBOX_TEXT_OPSW" + number + "_THROWN"), 479 Bundle.getMessage("COMBOBOX_TEXT_OPSW" + number + "_CLOSED")}; 480 comboBox[n] = new JComboBox<>(s); 481 comboBox[n].setSelectedIndex(getIndexForDefault(n)); 482 // size all combos to match the widest one (and a little bit more) 483 comboBox[n].setPreferredSize(new JLabel("XXXXXXX" + Bundle.getMessage("COMBOBOX_TEXT_OPSW36_THROWN")).getPreferredSize()); // NOI18N 484 // layout 485 gc.gridx = 1; 486 gc.weightx = 1.0; 487 gc.anchor = GridBagConstraints.WEST; 488 gl.setConstraints(comboBox[n], gc); 489 return comboBox[n]; 490 } 491 492 /** 493 * Create a JComboBox with four possible values. 494 * <p> 495 * For two given OpSw numbers, create a JComboBox containing the appropriate 496 * strings from the bundle. Sets the initial value based on the OpSws' 497 * reported default value. 498 * 499 * @param n first OpSw number 500 * @param n2 second OpSw number 501 * @return the JPanel into which the JComboBox is placed 502 */ 503 private JComboBox<String> getComboBox(int n, int n2) { 504 String number = Integer.toString(n); 505 if (number.length() == 1) { 506 number = "0" + number; // NOI18N 507 } 508 String number2 = Integer.toString(n2); 509 if (number2.length() == 1) { 510 number2 = "0" + number2; // NOI18N 511 } 512 513 comboBox[n] = new JComboBox<>( 514 new String[] {Bundle.getMessage("COMBOBOX_TEXT_OPSW" + number + "_THROWN_OPSW" + number2 + "_THROWN"), 515 Bundle.getMessage("COMBOBOX_TEXT_OPSW" + number + "_CLOSED_OPSW" + number2 + "_THROWN"), 516 Bundle.getMessage("COMBOBOX_TEXT_OPSW" + number + "_THROWN_OPSW" + number2 + "_CLOSED"), 517 Bundle.getMessage("COMBOBOX_TEXT_OPSW" + number + "_CLOSED_OPSW" + number2 + "_CLOSED") 518 }); 519 // default choice = 0 so already set 520 // size all combos to match the widest one (and a little bit more) 521 comboBox[n].setPreferredSize(new JLabel("XXXXXXX" + Bundle.getMessage("COMBOBOX_TEXT_OPSW36_THROWN")).getPreferredSize()); // NOI18N 522 // layout 523 gc.gridx = 1; 524 gc.weightx = 1.0; 525 gc.anchor = GridBagConstraints.WEST; 526 gl.setConstraints(comboBox[n], gc); 527 return comboBox[n]; 528 } 529 530 /** 531 * Determine the JComboBox index which corresponds to the default value 532 * for a given OpSw. 533 * 534 * @param n OpSw number 535 * @return index of default choice 536 */ 537 private int getIndexForDefault(int n) { 538 switch (n) { 539 case 5: 540 case 11: 541 case 12: 542 return 1; 543 default: 544 return 0; 545 } 546 } 547 548 /** 549 * Already know of this board (unit address)? 550 * 551 * @param id Unit address to be checked against list 552 * @return True if the unit address is already in the list 553 */ 554 private boolean alreadyKnowThisBoardId(Integer id) { 555 return (boardNumsEntryValue.contains(id)); 556 } 557 558 /** 559 * Add a board to the list of unit addresses if not already there. 560 * 561 * @param id a unit address to be added 562 * @return index into the boardNumsEntryValue list of entry for unit address "id" 563 */ 564 private Integer addBoardIdToList(Integer id) { 565 boardNumsEntryValue.add(boardNumsEntryValue.size(), id); 566 addressComboBox.removeAllItems(); 567 Collections.sort(boardNumsEntryValue); 568 Integer indexOfTargetBoardAddress = 0; 569 for (Integer index = 0; index < boardNumsEntryValue.size(); ++index) { 570 if (boardNumsEntryValue.get(index).equals(id)) { 571 indexOfTargetBoardAddress = index; 572 } 573 addressComboBox.addItem(boardNumsEntryValue.get(index)); 574 } 575 return indexOfTargetBoardAddress; 576 } 577 578 /** 579 * Select a device based on an index into the list of unit addresses. 580 * 581 * @param index into the list of addresses 582 */ 583 private void selectBoardIdByIndex(Integer index) { 584 addressComboBox.setSelectedIndex(index); 585 } 586 587 /** 588 * Read all OpSws, based on the selected unit address in the JComboBox. 589 */ 590 @Override 591 public void readAll() { 592 addrField.setText(addressComboBox.getSelectedItem().toString()); 593 Integer curAddr = Integer.parseInt(addrField.getText()); 594 595 // If a new board address is specified, add it (and sort it) into the current list. 596 if (!alreadyKnowThisBoardId(curAddr)) { 597 Integer index = addBoardIdToList(curAddr); 598 selectBoardIdByIndex(index); 599 } 600 super.readAll(); 601 } 602 603 /** 604 * Interpret incoming LocoNet messages. 605 * 606 * @param m LocoNet message to be interpreted 607 */ 608 @Override 609 public void message(LocoNetMessage m) { 610 super.message(m); 611 if ((m.getOpCode() == LnConstants.OPC_MULTI_SENSE) && ((m.getElement(1) & 0x7E) == 0x62)) { 612 // device identity report 613 if (m.getElement(3) == 0x01) { 614 Integer extractedBoardId = 1 + ((m.getElement(1) & 0x1) << 7) 615 + (m.getElement(2) & 0x7F); 616 if (!alreadyKnowThisBoardId(extractedBoardId)) { 617 addBoardIdToList(extractedBoardId); 618 } 619 } 620 } 621 } 622 623 private final static Logger log = LoggerFactory.getLogger(BDL16Panel.class); 624 625}