001package jmri.jmrit.symbolicprog.tabbedframe; 002 003import java.awt.*; 004import java.awt.event.ItemEvent; 005import java.awt.event.ItemListener; 006import java.io.IOException; 007import java.lang.reflect.Field; 008import java.util.*; 009import java.util.List; 010 011import javax.swing.*; 012import javax.swing.table.TableModel; 013import javax.swing.table.TableRowSorter; 014 015import org.jdom2.Attribute; 016import org.jdom2.Element; 017 018import jmri.jmrit.roster.RosterEntry; 019import jmri.jmrit.symbolicprog.*; 020import jmri.util.*; 021import jmri.util.davidflanagan.HardcopyWriter; 022import jmri.util.jdom.LocaleSelector; 023 024/** 025 * Provide the individual panes for the TabbedPaneProgrammer. 026 * <p> 027 * Note that this is not only the panes carrying variables, but also the special 028 * purpose panes for the CV table, etc. 029 * <p> 030 * This class implements PropertyChangeListener so that it can be notified when 031 * a variable changes its busy status at the end of a programming read/write 032 * operation. 033 * 034 * There are four read and write operation types, all of which have to be 035 * handled carefully: 036 * <DL> 037 * <DT>Write Changes<DD>This must write changes that occur after the operation 038 * starts, because the act of writing a variable/CV may change another. For 039 * example, writing CV 1 will mark CV 29 as changed. 040 * <p> 041 * The definition of "changed" is operationally in the 042 * {@link jmri.jmrit.symbolicprog.VariableValue#isChanged} member function. 043 * 044 * <DT>Write All<DD>Like write changes, this might have to go back and re-write 045 * a variable depending on what has previously happened. It should write every 046 * variable (at least) once. 047 * <DT>Read All<DD>This should read every variable once. 048 * <img src="doc-files/PaneProgPane-ReadAllSequenceDiagram.png" alt="UML Sequence diagram"> 049 * <DT>Read Changes<DD>This should read every variable that's marked as changed. 050 * Currently, we use a common definition of changed with the write operations, 051 * and that someday might have to change. 052 * 053 * </DL> 054 * 055 * @author Bob Jacobsen Copyright (C) 2001, 2003, 2004, 2005, 2006 056 * @author D Miller Copyright 2003 057 * @author Howard G. Penny Copyright (C) 2005 058 * @author Dave Heap Copyright (C) 2014, 2019 059 * @see jmri.jmrit.symbolicprog.VariableValue#isChanged 060 */ 061/* 062 * @startuml jmri/jmrit/symbolicprog/tabbedframe/doc-files/PaneProgPane-ReadAllSequenceDiagram.png 063 * actor User 064 * box "PaneProgPane" 065 * participant readPaneAll 066 * participant prepReadPane 067 * participant nextRead 068 * participant executeRead 069 * participant propertyChange 070 * participant replyWhileProgrammingVar 071 * participant restartProgramming 072 * end box 073 * box "VariableValue" 074 * participant readAll 075 * participant readChanges 076 * end box 077 * 078 * control Programmer 079 * User -> readPaneAll: Read All Sheets 080 * activate readPaneAll 081 * readPaneAll -> prepReadPane 082 * activate prepReadPane 083 * prepReadPane --> readPaneAll 084 * deactivate prepReadPane 085 * deactivate prepReadPane 086 * readPaneAll -> nextRead 087 * activate nextRead 088 * nextRead -> executeRead 089 * activate executeRead 090 * executeRead -> readAll 091 * activate readAll 092 * readAll -> Programmer 093 * activate Programmer 094 * readAll --> executeRead 095 * deactivate readAll 096 * executeRead --> nextRead 097 * deactivate executeRead 098 * nextRead --> readPaneAll 099 * deactivate nextRead 100 * deactivate readPaneAll 101 * == Callback after read completes == 102 * Programmer -> propertyChange 103 * activate propertyChange 104 * note over propertyChange 105 * if the first read failed, 106 * setup a second read of 107 * the same value. 108 * otherwise, setup a read of 109 * the next value. 110 * end note 111 * deactivate Programmer 112 * propertyChange -> User: CV value or error 113 * propertyChange -> replyWhileProgrammingVar 114 * activate replyWhileProgrammingVar 115 * replyWhileProgrammingVar -> restartProgramming 116 * activate restartProgramming 117 * restartProgramming -> nextRead 118 * activate nextRead 119 * nextRead -> executeRead 120 * activate executeRead 121 * executeRead -> readAll 122 * activate readAll 123 * readAll -> Programmer 124 * activate Programmer 125 * readAll --> executeRead 126 * deactivate readAll 127 * executeRead -> nextRead 128 * deactivate executeRead 129 * nextRead --> restartProgramming 130 * deactivate nextRead 131 * restartProgramming --> replyWhileProgrammingVar 132 * deactivate restartProgramming 133 * replyWhileProgrammingVar --> propertyChange 134 * deactivate replyWhileProgrammingVar 135 * deactivate propertyChange 136 * deactivate Programmer 137 * == Callback triggered repeat occurs until no more values == 138 * @enduml 139 */ 140public class PaneProgPane extends javax.swing.JPanel 141 implements java.beans.PropertyChangeListener { 142 143 static final String LAST_GRIDX = "last_gridx"; 144 static final String LAST_GRIDY = "last_gridy"; 145 146 protected CvTableModel _cvModel; 147 protected VariableTableModel _varModel; 148 protected PaneContainer container; 149 protected RosterEntry rosterEntry; 150 151 boolean _cvTable; 152 153 protected JPanel bottom; 154 155 transient ItemListener l1; 156 protected transient ItemListener l2; 157 transient ItemListener l3; 158 protected transient ItemListener l4; 159 transient ItemListener l5; 160 transient ItemListener l6; 161 162 boolean isCvTablePane = false; 163 164 /** 165 * Store name of this programmer Tab (pane) 166 */ 167 String mName = ""; 168 169 /** 170 * Construct a null object. 171 * <p> 172 * Normally only used for tests and to pre-load classes. 173 */ 174 public PaneProgPane() { 175 } 176 177 public PaneProgPane(PaneContainer parent, String name, Element pane, CvTableModel cvModel, VariableTableModel varModel, Element modelElem, RosterEntry pRosterEntry) { 178 this(parent, name, pane, cvModel, varModel, modelElem, pRosterEntry, false); 179 } 180 181 /** 182 * Construct the Pane from the XML definition element. 183 * 184 * In case this is invoked from off the Swing/AWT thread, 185 * it defers to that thread in a granular manner. 186 * 187 * @param parent The parent pane 188 * @param name Name to appear on tab of pane 189 * @param pane The JDOM Element for the pane definition 190 * @param cvModel Already existing TableModel containing the CV 191 * definitions 192 * @param varModel Already existing TableModel containing the variable 193 * definitions 194 * @param modelElem "model" element from the Decoder Index, used to check 195 * what decoder options are present. 196 * @param pRosterEntry The current roster entry, used to get sound labels. 197 * @param isProgPane True if the pane is a default programmer pane 198 */ 199 public PaneProgPane(PaneContainer parent, String name, Element pane, CvTableModel cvModel, VariableTableModel varModel, Element modelElem, RosterEntry pRosterEntry, boolean isProgPane) { 200 201 container = parent; 202 mName = name; 203 _cvModel = cvModel; 204 _varModel = varModel; 205 rosterEntry = pRosterEntry; 206 207 // when true a cv table with compare was loaded into pane 208 _cvTable = false; 209 210 // This is a JPanel containing a JScrollPane, containing a 211 // laid-out JPanel 212 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 213 214 // Add tooltip (if available) 215 setToolTipText(jmri.util.jdom.LocaleSelector.getAttribute(pane, "tooltip")); 216 217 // find out whether to display "label" (false) or "item" (true) 218 Attribute nameFmt = pane.getAttribute("nameFmt"); 219 final boolean showItem = (nameFmt != null && nameFmt.getValue().equals("item")); 220 if (showItem) { 221 log.debug("Pane {} will show items, not labels, from decoder file", name); 222 } 223 // put the columns left to right in a panel 224 JPanel p = new JPanel(); 225 panelList.add(p); 226 p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); 227 228 // handle the xml definition 229 // for all "column" elements ... 230 List<Element> colList = pane.getChildren("column"); 231 for (Element element : colList) { 232 ThreadingUtil.runOnGUI(() -> { 233 // load each column 234 p.add(newColumn(element, showItem, modelElem)); 235 }); 236 } 237 // for all "row" elements ... 238 List<Element> rowList = pane.getChildren("row"); 239 for (Element element : rowList) { 240 ThreadingUtil.runOnGUI(() -> { 241 // load each row 242 p.add(newRow(element, showItem, modelElem)); 243 }); 244 } 245 // for all "grid" elements ... 246 List<Element> gridList = pane.getChildren("grid"); 247 for (Element element : gridList) { 248 ThreadingUtil.runOnGUI(() -> { 249 // load each grid 250 p.add(newGrid(element, showItem, modelElem)); 251 }); 252 } 253 // for all "group" elements ... 254 List<Element> groupList = pane.getChildren("group"); 255 for (Element element : groupList) { 256 ThreadingUtil.runOnGUI(() -> { 257 // load each group 258 p.add(newGroup(element, showItem, modelElem)); 259 }); 260 } 261 262 // explain why pane is empty 263 if (cvList.isEmpty() && varList.isEmpty() && isProgPane) { 264 JPanel pe = new JPanel(); 265 pe.setLayout(new BoxLayout(pe, BoxLayout.Y_AXIS)); 266 int line = 1; 267 while (line >= 0) { 268 try { 269 String msg = SymbolicProgBundle.getMessage("TextTabEmptyExplain" + line); 270 if (msg.isEmpty()) { 271 msg = " "; 272 } 273 JLabel l = new JLabel(msg); 274 l.setAlignmentX(Component.CENTER_ALIGNMENT); 275 pe.add(l); 276 line++; 277 } catch (java.util.MissingResourceException e) { // deliberately runs until exception 278 line = -1; 279 } 280 } 281 add(pe); 282 panelList.add(pe); 283 return; 284 } 285 286 // add glue to the right to allow resize - but this isn't working as expected? Alignment? 287 add(Box.createHorizontalGlue()); 288 289 add(new JScrollPane(p)); 290 291 // add buttons in a new panel 292 bottom = new JPanel(); 293 panelList.add(p); 294 bottom.setLayout(new BoxLayout(bottom, BoxLayout.X_AXIS)); 295 296 // enable read buttons, if possible, and 297 // set their tool tips 298 enableReadButtons(); 299 300 // add read button listeners 301 readChangesButton.addItemListener(l1 = (ItemEvent e) -> { 302 if (e.getStateChange() == ItemEvent.SELECTED) { 303 readChangesButton.setText(SymbolicProgBundle.getMessage("ButtonStopReadChangesSheet")); 304 if (!container.isBusy()) { 305 prepReadPane(true); 306 prepGlassPane(readChangesButton); 307 container.getBusyGlassPane().setVisible(true); 308 readPaneChanges(); 309 } 310 } else { 311 stopProgramming(); 312 readChangesButton.setText(SymbolicProgBundle.getMessage("ButtonReadChangesSheet")); 313 if (container.isBusy()) { 314 readChangesButton.setEnabled(false); 315 } 316 } 317 }); 318 readAllButton.addItemListener(l2 = (ItemEvent e) -> { 319 if (e.getStateChange() == ItemEvent.SELECTED) { 320 readAllButton.setText(SymbolicProgBundle.getMessage("ButtonStopReadSheet")); 321 if (!container.isBusy()) { 322 prepReadPane(false); 323 prepGlassPane(readAllButton); 324 container.getBusyGlassPane().setVisible(true); 325 readPaneAll(); 326 } 327 } else { 328 stopProgramming(); 329 readAllButton.setText(SymbolicProgBundle.getMessage("ButtonReadFullSheet")); 330 if (container.isBusy()) { 331 readAllButton.setEnabled(false); 332 } 333 } 334 }); 335 336 writeChangesButton.setToolTipText(SymbolicProgBundle.getMessage("TipWriteHighlightedSheet")); 337 writeChangesButton.addItemListener(l3 = (ItemEvent e) -> { 338 if (e.getStateChange() == ItemEvent.SELECTED) { 339 writeChangesButton.setText(SymbolicProgBundle.getMessage("ButtonStopWriteChangesSheet")); 340 if (!container.isBusy()) { 341 prepWritePane(true); 342 prepGlassPane(writeChangesButton); 343 container.getBusyGlassPane().setVisible(true); 344 writePaneChanges(); 345 } 346 } else { 347 stopProgramming(); 348 writeChangesButton.setText(SymbolicProgBundle.getMessage("ButtonWriteChangesSheet")); 349 if (container.isBusy()) { 350 writeChangesButton.setEnabled(false); 351 } 352 } 353 }); 354 355 writeAllButton.setToolTipText(SymbolicProgBundle.getMessage("TipWriteAllSheet")); 356 writeAllButton.addItemListener(l4 = (ItemEvent e) -> { 357 if (e.getStateChange() == ItemEvent.SELECTED) { 358 writeAllButton.setText(SymbolicProgBundle.getMessage("ButtonStopWriteSheet")); 359 if (!container.isBusy()) { 360 prepWritePane(false); 361 prepGlassPane(writeAllButton); 362 container.getBusyGlassPane().setVisible(true); 363 writePaneAll(); 364 } 365 } else { 366 stopProgramming(); 367 writeAllButton.setText(SymbolicProgBundle.getMessage("ButtonWriteFullSheet")); 368 if (container.isBusy()) { 369 writeAllButton.setEnabled(false); 370 } 371 } 372 }); 373 374 // enable confirm buttons, if possible, and 375 // set their tool tips 376 enableConfirmButtons(); 377 378 // add confirm button listeners 379 confirmChangesButton.addItemListener(l5 = (ItemEvent e) -> { 380 if (e.getStateChange() == ItemEvent.SELECTED) { 381 confirmChangesButton.setText(SymbolicProgBundle.getMessage("ButtonStopConfirmChangesSheet")); 382 if (!container.isBusy()) { 383 prepConfirmPane(true); 384 prepGlassPane(confirmChangesButton); 385 container.getBusyGlassPane().setVisible(true); 386 confirmPaneChanges(); 387 } 388 } else { 389 stopProgramming(); 390 confirmChangesButton.setText(SymbolicProgBundle.getMessage("ButtonConfirmChangesSheet")); 391 if (container.isBusy()) { 392 confirmChangesButton.setEnabled(false); 393 } 394 } 395 }); 396 confirmAllButton.addItemListener(l6 = (ItemEvent e) -> { 397 if (e.getStateChange() == ItemEvent.SELECTED) { 398 confirmAllButton.setText(SymbolicProgBundle.getMessage("ButtonStopConfirmSheet")); 399 if (!container.isBusy()) { 400 prepConfirmPane(false); 401 prepGlassPane(confirmAllButton); 402 container.getBusyGlassPane().setVisible(true); 403 confirmPaneAll(); 404 } 405 } else { 406 stopProgramming(); 407 confirmAllButton.setText(SymbolicProgBundle.getMessage("ButtonConfirmFullSheet")); 408 if (container.isBusy()) { 409 confirmAllButton.setEnabled(false); 410 } 411 } 412 }); 413 414// Only add change buttons to CV tables 415 bottom.add(readChangesButton); 416 bottom.add(writeChangesButton); 417 if (_cvTable) { 418 bottom.add(confirmChangesButton); 419 } 420 bottom.add(readAllButton); 421 bottom.add(writeAllButton); 422 if (_cvTable) { 423 bottom.add(confirmAllButton); 424 } 425 426 // don't show buttons if no programmer at all 427 if (_cvModel.getProgrammer() != null) { 428 add(bottom); 429 } 430 } 431 432 public void setNoDecoder() { 433 readChangesButton.setEnabled(false); 434 readAllButton.setEnabled(false); 435 writeChangesButton.setEnabled(false); 436 writeAllButton.setEnabled(false); 437 confirmChangesButton.setEnabled(false); 438 confirmAllButton.setEnabled(false); 439 } 440 441 @Override 442 public String getName() { 443 return mName; 444 } 445 446 @Override 447 public String toString() { 448 return getName(); 449 } 450 451 /** 452 * Enable the read all and read changes button if possible. This checks to 453 * make sure this is appropriate, given the attached programmer's 454 * capability. 455 */ 456 void enableReadButtons() { 457 readChangesButton.setToolTipText(SymbolicProgBundle.getMessage("TipReadChangesSheet")); 458 readAllButton.setToolTipText(SymbolicProgBundle.getMessage("TipReadAllSheet")); 459 if (_cvModel.getProgrammer() != null 460 && !_cvModel.getProgrammer().getCanRead()) { 461 // can't read, disable the buttons 462 readChangesButton.setEnabled(false); 463 readAllButton.setEnabled(false); 464 // set tooltip to explain why 465 readChangesButton.setToolTipText(SymbolicProgBundle.getMessage("TipNoRead")); 466 readAllButton.setToolTipText(SymbolicProgBundle.getMessage("TipNoRead")); 467 } else { 468 readChangesButton.setEnabled(true); 469 readAllButton.setEnabled(true); 470 } 471 } 472 473 /** 474 * Enable the compare all and compare changes button if possible. This 475 * checks to make sure this is appropriate, given the attached programmer's 476 * capability. 477 */ 478 void enableConfirmButtons() { 479 confirmChangesButton.setToolTipText(SymbolicProgBundle.getMessage("TipConfirmChangesSheet")); 480 confirmAllButton.setToolTipText(SymbolicProgBundle.getMessage("TipConfirmAllSheet")); 481 if (_cvModel.getProgrammer() != null 482 && !_cvModel.getProgrammer().getCanRead()) { 483 // can't read, disable the buttons 484 confirmChangesButton.setEnabled(false); 485 confirmAllButton.setEnabled(false); 486 // set tooltip to explain why 487 confirmChangesButton.setToolTipText(SymbolicProgBundle.getMessage("TipNoRead")); 488 confirmAllButton.setToolTipText(SymbolicProgBundle.getMessage("TipNoRead")); 489 } else { 490 confirmChangesButton.setEnabled(true); 491 confirmAllButton.setEnabled(true); 492 } 493 } 494 495 /** 496 * This remembers the variables on this pane for the Read/Write sheet 497 * operation. They are stored as a list of Integer objects, each of which is 498 * the index of the Variable in the VariableTable. 499 */ 500 List<Integer> varList = new ArrayList<>(); 501 int varListIndex; 502 /** 503 * This remembers the CVs on this pane for the Read/Write sheet operation. 504 * They are stored as a set of Integer objects, each of which is the index 505 * of the CV in the CVTable. Note that variables are handled separately, and 506 * the CVs that are represented by variables are not entered here. So far 507 * (sic), the only use of this is for the cvtable rep. 508 */ 509 protected TreeSet<Integer> cvList = new TreeSet<>(); // TreeSet is iterated in order 510 protected Iterator<Integer> cvListIterator; 511 512 protected JToggleButton readChangesButton = new JToggleButton(SymbolicProgBundle.getMessage("ButtonReadChangesSheet")); 513 protected JToggleButton readAllButton = new JToggleButton(SymbolicProgBundle.getMessage("ButtonReadFullSheet")); 514 protected JToggleButton writeChangesButton = new JToggleButton(SymbolicProgBundle.getMessage("ButtonWriteChangesSheet")); 515 protected JToggleButton writeAllButton = new JToggleButton(SymbolicProgBundle.getMessage("ButtonWriteFullSheet")); 516 JToggleButton confirmChangesButton = new JToggleButton(SymbolicProgBundle.getMessage("ButtonConfirmChangesSheet")); 517 JToggleButton confirmAllButton = new JToggleButton(SymbolicProgBundle.getMessage("ButtonConfirmFullSheet")); 518 519 /** 520 * Estimate the number of CVs that will be accessed when reading or writing 521 * the contents of this pane. 522 * 523 * @param read true if counting for read, false for write 524 * @param changes true if counting for a *Changes operation; false, if 525 * counting for a *All operation 526 * @return the total number of CV reads/writes needed for this pane 527 */ 528 public int countOpsNeeded(boolean read, boolean changes) { 529 Set<Integer> set = new HashSet<>(cvList.size() + varList.size() + 50); 530 return makeOpsNeededSet(read, changes, set).size(); 531 } 532 533 /** 534 * Produce a set of CVs that will be accessed when reading or writing the 535 * contents of this pane. 536 * 537 * @param read true if counting for read, false for write 538 * @param changes true if counting for a *Changes operation; false, if 539 * counting for a *All operation 540 * @param set The set to fill. Any CVs already in here will not be 541 * duplicated, which provides a way to aggregate a set of CVs 542 * across multiple panes. 543 * @return the same set as the parameter, for convenient chaining of 544 * operations. 545 */ 546 public Set<Integer> makeOpsNeededSet(boolean read, boolean changes, Set<Integer> set) { 547 548 // scan the variable list 549 for (int varNum : varList) { 550 551 VariableValue var = _varModel.getVariable(varNum); 552 553 // must decide whether this one should be counted 554 if (!changes || var.isChanged()) { 555 556 CvValue[] cvs = var.usesCVs(); 557 for (CvValue cv : cvs) { 558 // always of interest 559 if (!changes || VariableValue.considerChanged(cv)) { 560 set.add(Integer.valueOf(cv.number())); 561 } 562 } 563 } 564 } 565 566 return set; 567 } 568 569 private void prepGlassPane(AbstractButton activeButton) { 570 container.prepGlassPane(activeButton); 571 } 572 573 void enableButtons(boolean stat) { 574 if (stat) { 575 enableReadButtons(); 576 enableConfirmButtons(); 577 } else { 578 readChangesButton.setEnabled(stat); 579 readAllButton.setEnabled(stat); 580 confirmChangesButton.setEnabled(stat); 581 confirmAllButton.setEnabled(stat); 582 } 583 writeChangesButton.setEnabled(stat); 584 writeAllButton.setEnabled(stat); 585 } 586 587 boolean justChanges; 588 589 /** 590 * Invoked by "Read changes on sheet" button, this sets in motion a 591 * continuing sequence of "read" operations on the variables and 592 * CVs in the Pane. Only variables in states marked as "changed" will be 593 * read. 594 * 595 * @return true is a read has been started, false if the pane is complete. 596 */ 597 public boolean readPaneChanges() { 598 if (log.isDebugEnabled()) { 599 log.debug("readPane starts with {} vars, {} cvs ", varList.size(), cvList.size()); 600 } 601 prepReadPane(true); 602 return nextRead(); 603 } 604 605 /** 606 * Prepare this pane for a read operation. 607 * <p> 608 * The read mechanism only reads variables in certain states (and needs to 609 * do that to handle error processing right now), so this is implemented by 610 * first setting all variables and CVs on this pane to TOREAD via this 611 * method 612 * 613 * @param onlyChanges true if only reading changes; false if reading all 614 */ 615 public void prepReadPane(boolean onlyChanges) { 616 log.debug("start prepReadPane with onlyChanges={}", onlyChanges); 617 justChanges = onlyChanges; 618 619 if (isCvTablePane) { 620 setCvListFromTable(); // make sure list of CVs up to date if table 621 } 622 enableButtons(false); 623 if (justChanges) { 624 readChangesButton.setEnabled(true); 625 readChangesButton.setSelected(true); 626 } else { 627 readAllButton.setSelected(true); 628 readAllButton.setEnabled(true); 629 } 630 if (!container.isBusy()) { 631 container.enableButtons(false); 632 } 633 setToRead(justChanges, true); 634 varListIndex = 0; 635 cvListIterator = cvList.iterator(); 636 cvReadSoFar = 0 ; 637 cvReadStartTime = System.currentTimeMillis(); 638 } 639 640 /** 641 * Invoked by "Read Full Sheet" button, this sets in motion a continuing 642 * sequence of "read" operations on the variables and CVs in the 643 * Pane. The read mechanism only reads variables in certain states (and 644 * needs to do that to handle error processing right now), so this is 645 * implemented by first setting all variables and CVs on this pane to TOREAD 646 * in prepReadPaneAll, then starting the execution. 647 * 648 * @return true is a read has been started, false if the pane is complete 649 */ 650 public boolean readPaneAll() { 651 if (log.isDebugEnabled()) { 652 log.debug("readAllPane starts with {} vars, {} cvs ", varList.size(), cvList.size()); 653 } 654 prepReadPane(false); 655 // start operation 656 return nextRead(); 657 } 658 659 /** 660 * Set the "ToRead" parameter in all variables and CVs on this pane. 661 * 662 * @param justChanges true if this is read changes, false if read all 663 * @param startProcess true if this is the start of processing, false if 664 * cleaning up at end 665 */ 666 void setToRead(boolean justChanges, boolean startProcess) { 667 if (!container.isBusy() 668 || // the frame has already setToRead 669 (!startProcess)) { // we want to setToRead false if the pane's process is being stopped 670 for (int varNum : varList) { 671 VariableValue var = _varModel.getVariable(varNum); 672 if (justChanges) { 673 if (var.isChanged()) { 674 var.setToRead(startProcess); 675 } else { 676 var.setToRead(false); 677 } 678 } else { 679 var.setToRead(startProcess); 680 } 681 } 682 683 if (isCvTablePane) { 684 setCvListFromTable(); // make sure list of CVs up to date if table 685 } 686 for (int cvNum : cvList) { 687 CvValue cv = _cvModel.getCvByRow(cvNum); 688 if (justChanges) { 689 if (VariableValue.considerChanged(cv)) { 690 cv.setToRead(startProcess); 691 } else { 692 cv.setToRead(false); 693 } 694 } else { 695 cv.setToRead(startProcess); 696 } 697 } 698 } 699 } 700 701 /** 702 * Set the "ToWrite" parameter in all variables and CVs on this pane 703 * 704 * @param justChanges true if this is read changes, false if read all 705 * @param startProcess true if this is the start of processing, false if 706 * cleaning up at end 707 */ 708 void setToWrite(boolean justChanges, boolean startProcess) { 709 log.debug("start setToWrite method with {},{}", justChanges, startProcess); 710 if (!container.isBusy() 711 || // the frame has already setToWrite 712 (!startProcess)) { // we want to setToWrite false if the pane's process is being stopped 713 log.debug("about to start setToWrite of varList"); 714 for (int varNum : varList) { 715 VariableValue var = _varModel.getVariable(varNum); 716 if (justChanges) { 717 if (var.isChanged()) { 718 var.setToWrite(startProcess); 719 } else { 720 var.setToWrite(false); 721 } 722 } else { 723 var.setToWrite(startProcess); 724 } 725 } 726 727 log.debug("about to start setToWrite of cvList"); 728 if (isCvTablePane) { 729 setCvListFromTable(); // make sure list of CVs up to date if table 730 } 731 for (int cvNum : cvList) { 732 CvValue cv = _cvModel.getCvByRow(cvNum); 733 if (justChanges) { 734 if (VariableValue.considerChanged(cv)) { 735 cv.setToWrite(startProcess); 736 } else { 737 cv.setToWrite(false); 738 } 739 } else { 740 cv.setToWrite(startProcess); 741 } 742 } 743 } 744 log.debug("end setToWrite method"); 745 } 746 747 void executeRead(VariableValue var) { 748 setBusy(true); 749 // var.setToRead(false); // variables set this themselves 750 if (_programmingVar != null) { 751 log.error("listener already set at read start"); 752 } 753 _programmingVar = var; 754 _read = true; 755 // get notified when that state changes so can repeat 756 _programmingVar.addPropertyChangeListener(this); 757 // and make the read request 758 if (justChanges) { 759 _programmingVar.readChanges(); 760 } else { 761 _programmingVar.readAll(); 762 } 763 } 764 765 void executeWrite(VariableValue var) { 766 setBusy(true); 767 // var.setToWrite(false); // variables reset themselves when done 768 if (_programmingVar != null) { 769 log.error("listener already set at write start"); 770 } 771 _programmingVar = var; 772 _read = false; 773 // get notified when that state changes so can repeat 774 _programmingVar.addPropertyChangeListener(this); 775 // and make the write request 776 if (justChanges) { 777 _programmingVar.writeChanges(); 778 } else { 779 _programmingVar.writeAll(); 780 } 781 } 782 783 // keep track of multi reads. 784 long cvReadSoFar; 785 long cvReadStartTime; 786 787 /** 788 * If there are any more read operations to be done on this pane, do the 789 * next one. 790 * <p> 791 * Each invocation of this method reads one variable or CV; completion of 792 * that request will cause it to happen again, reading the next one, until 793 * there's nothing left to read. 794 * @return true is a read has been started, false if the pane is complete. 795 */ 796 boolean nextRead() { 797 // look for possible variables 798 if (log.isDebugEnabled()) { 799 log.debug("nextRead scans {} variables", varList.size()); 800 } 801 while ((varList.size() > 0) && (varListIndex < varList.size())) { 802 int varNum = varList.get(varListIndex); 803 AbstractValue.ValueState vState = _varModel.getState(varNum); 804 VariableValue var = _varModel.getVariable(varNum); 805 if (log.isDebugEnabled()) { 806 log.debug("nextRead var index {} state {} isToRead: {} label: {}", varNum, vState.getName(), var.isToRead(), var.label()); 807 } 808 varListIndex++; 809 if (var.isToRead()) { 810 if (log.isDebugEnabled()) { 811 log.debug("start read of variable {}", _varModel.getLabel(varNum)); 812 } 813 executeRead(var); 814 815 log.debug("return from starting var read"); 816 // the request may have instantaneously been satisfied... 817 return true; // only make one request at a time! 818 } 819 } 820 // found no variables needing read, try CVs 821 if (log.isDebugEnabled()) { 822 log.debug("nextRead scans {} CVs", cvList.size()); 823 } 824 825 while (cvListIterator != null && cvListIterator.hasNext()) { 826 int cvNum = cvListIterator.next(); 827 cvReadSoFar++; 828 CvValue cv = _cvModel.getCvByRow(cvNum); 829 if (log.isDebugEnabled()) { 830 log.debug("nextRead cv index {} state {}", cvNum, cv.getState()); 831 } 832 833 if (cv.isToRead()) { // always read UNKNOWN state 834 log.debug("start read of cv {}", cvNum); 835 setBusy(true); 836 if (_programmingCV != null) { 837 log.error("listener already set at read start"); 838 } 839 _programmingCV = _cvModel.getCvByRow(cvNum); 840 _read = true; 841 // get notified when that state changes so can repeat 842 _programmingCV.addPropertyChangeListener(this); 843 // and make the read request 844 // _programmingCV.setToRead(false); // CVs set this themselves 845 _programmingCV.read(_cvModel.getStatusLabel(), cvReadSoFar, cvList.size(), cvReadStartTime); 846 log.debug("return from starting CV read"); 847 // the request may have instantateously been satisfied... 848 return true; // only make one request at a time! 849 } 850 } 851 // nothing to program, end politely 852 log.debug("nextRead found nothing to do"); 853 readChangesButton.setSelected(false); 854 readAllButton.setSelected(false); // reset both, as that's final state we want 855 setBusy(false); 856 container.paneFinished(); 857 return false; 858 } 859 860 /** 861 * If there are any more compare operations to be done on this pane, do the 862 * next one. 863 * <p> 864 * Each invocation of this method compares one CV; completion of that request 865 * will cause it to happen again, reading the next one, until there's 866 * nothing left to read. 867 * 868 * @return true is a compare has been started, false if the pane is 869 * complete. 870 */ 871 boolean nextConfirm() { 872 // look for possible CVs 873 while (cvListIterator != null && cvListIterator.hasNext()) { 874 int cvNum = cvListIterator.next(); 875 CvValue cv = _cvModel.getCvByRow(cvNum); 876 if (log.isDebugEnabled()) { 877 log.debug("nextConfirm cv index {} state {}", cvNum, cv.getState()); 878 } 879 880 if (cv.isToRead()) { 881 log.debug("start confirm of cv {}", cvNum); 882 setBusy(true); 883 if (_programmingCV != null) { 884 log.error("listener already set at confirm start"); 885 } 886 _programmingCV = _cvModel.getCvByRow(cvNum); 887 _read = true; 888 // get notified when that state changes so can repeat 889 _programmingCV.addPropertyChangeListener(this); 890 // and make the compare request 891 _programmingCV.confirm(_cvModel.getStatusLabel()); 892 log.debug("return from starting CV confirm"); 893 // the request may have instantateously been satisfied... 894 return true; // only make one request at a time! 895 } 896 } 897 // nothing to program, end politely 898 log.debug("nextConfirm found nothing to do"); 899 confirmChangesButton.setSelected(false); 900 confirmAllButton.setSelected(false); // reset both, as that's final state we want 901 setBusy(false); 902 container.paneFinished(); 903 return false; 904 } 905 906 /** 907 * Invoked by "Write changes on sheet" button, this sets in motion a 908 * continuing sequence of "write" operations on the variables in the Pane. 909 * Only variables in isChanged states are written; other states don't need 910 * to be. 911 * 912 * @return true if a write has been started, false if the pane is complete 913 */ 914 public boolean writePaneChanges() { 915 log.debug("writePaneChanges starts"); 916 prepWritePane(true); 917 boolean val = nextWrite(); 918 log.debug("writePaneChanges returns {}", val); 919 return val; 920 } 921 922 /** 923 * Invoked by "Write full sheet" button to write all CVs. 924 * 925 * @return true if a write has been started, false if the pane is complete 926 */ 927 public boolean writePaneAll() { 928 prepWritePane(false); 929 return nextWrite(); 930 } 931 932 /** 933 * Prepare a "write full sheet" operation. 934 * 935 * @param onlyChanges true if only writing changes; false if writing all 936 */ 937 public void prepWritePane(boolean onlyChanges) { 938 log.debug("start prepWritePane with {}", onlyChanges); 939 justChanges = onlyChanges; 940 enableButtons(false); 941 942 if (isCvTablePane) { 943 setCvListFromTable(); // make sure list of CVs up to date if table 944 } 945 if (justChanges) { 946 writeChangesButton.setEnabled(true); 947 writeChangesButton.setSelected(true); 948 } else { 949 writeAllButton.setSelected(true); 950 writeAllButton.setEnabled(true); 951 } 952 if (!container.isBusy()) { 953 container.enableButtons(false); 954 } 955 setToWrite(justChanges, true); 956 varListIndex = 0; 957 958 cvListIterator = cvList.iterator(); 959 log.debug("end prepWritePane"); 960 } 961 962 boolean nextWrite() { 963 log.debug("start nextWrite"); 964 // look for possible variables 965 while ((varList.size() > 0) && (varListIndex < varList.size())) { 966 int varNum = varList.get(varListIndex); 967 AbstractValue.ValueState vState = _varModel.getState(varNum); 968 VariableValue var = _varModel.getVariable(varNum); 969 if (log.isDebugEnabled()) { 970 log.debug("nextWrite var index {} state {} isToWrite: {} label:{}", varNum, vState.getName(), var.isToWrite(), var.label()); 971 } 972 varListIndex++; 973 if (var.isToWrite()) { 974 log.debug("start write of variable {}", _varModel.getLabel(varNum)); 975 976 executeWrite(var); 977 978 log.debug("return from starting var write"); 979 return true; // only make one request at a time! 980 } 981 } 982 // check for CVs to handle (e.g. for CV table) 983 while (cvListIterator != null && cvListIterator.hasNext()) { 984 int cvNum = cvListIterator.next(); 985 CvValue cv = _cvModel.getCvByRow(cvNum); 986 if (log.isDebugEnabled()) { 987 log.debug("nextWrite cv index {} state {}", cvNum, cv.getState()); 988 } 989 990 if (cv.isToWrite()) { 991 log.debug("start write of cv index {}", cvNum); 992 setBusy(true); 993 if (_programmingCV != null) { 994 log.error("listener already set at write start"); 995 } 996 _programmingCV = _cvModel.getCvByRow(cvNum); 997 _read = false; 998 // get notified when that state changes so can repeat 999 _programmingCV.addPropertyChangeListener(this); 1000 // and make the write request 1001 // _programmingCV.setToWrite(false); // CVs set this themselves 1002 _programmingCV.write(_cvModel.getStatusLabel()); 1003 log.debug("return from starting cv write"); 1004 return true; // only make one request at a time! 1005 } 1006 } 1007 // nothing to program, end politely 1008 log.debug("nextWrite found nothing to do"); 1009 writeChangesButton.setSelected(false); 1010 writeAllButton.setSelected(false); 1011 setBusy(false); 1012 container.paneFinished(); 1013 log.debug("return from nextWrite with nothing to do"); 1014 return false; 1015 } 1016 1017 /** 1018 * Prepare this pane for a compare operation. 1019 * <p> 1020 * The read mechanism only reads variables in certain states (and needs to 1021 * do that to handle error processing right now), so this is implemented by 1022 * first setting all variables and CVs on this pane to TOREAD via this 1023 * method 1024 * 1025 * @param onlyChanges true if only confirming changes; false if confirming 1026 * all 1027 */ 1028 public void prepConfirmPane(boolean onlyChanges) { 1029 log.debug("start prepReadPane with onlyChanges={}", onlyChanges); 1030 justChanges = onlyChanges; 1031 enableButtons(false); 1032 1033 if (isCvTablePane) { 1034 setCvListFromTable(); // make sure list of CVs up to date if table 1035 } 1036 if (justChanges) { 1037 confirmChangesButton.setEnabled(true); 1038 confirmChangesButton.setSelected(true); 1039 } else { 1040 confirmAllButton.setSelected(true); 1041 confirmAllButton.setEnabled(true); 1042 } 1043 if (!container.isBusy()) { 1044 container.enableButtons(false); 1045 } 1046 // we can use the read prep since confirm has to read first 1047 setToRead(justChanges, true); 1048 varListIndex = 0; 1049 1050 cvListIterator = cvList.iterator(); 1051 } 1052 1053 /** 1054 * Invoked by "Compare changes on sheet" button, this sets in motion a 1055 * continuing sequence of "confirm" operations on the variables and 1056 * CVs in the Pane. Only variables in states marked as "changed" will be 1057 * checked. 1058 * 1059 * @return true is a confirm has been started, false if the pane is 1060 * complete. 1061 */ 1062 public boolean confirmPaneChanges() { 1063 if (log.isDebugEnabled()) { 1064 log.debug("confirmPane starts with {} vars, {} cvs ", varList.size(), cvList.size()); 1065 } 1066 prepConfirmPane(true); 1067 return nextConfirm(); 1068 } 1069 1070 /** 1071 * Invoked by "Compare Full Sheet" button, this sets in motion a continuing 1072 * sequence of "confirm" operations on the variables and CVs in the 1073 * Pane. The read mechanism only reads variables in certain states (and 1074 * needs to do that to handle error processing right now), so this is 1075 * implemented by first setting all variables and CVs on this pane to TOREAD 1076 * in prepReadPaneAll, then starting the execution. 1077 * 1078 * @return true is a confirm has been started, false if the pane is 1079 * complete. 1080 */ 1081 public boolean confirmPaneAll() { 1082 if (log.isDebugEnabled()) { 1083 log.debug("confirmAllPane starts with {} vars, {} cvs ", varList.size(), cvList.size()); 1084 } 1085 prepConfirmPane(false); 1086 // start operation 1087 return nextConfirm(); 1088 } 1089 1090 // reference to variable being programmed (or null if none) 1091 VariableValue _programmingVar = null; 1092 CvValue _programmingCV = null; 1093 boolean _read = true; 1094 1095 // busy during read, write operations 1096 private boolean _busy = false; 1097 1098 public boolean isBusy() { 1099 return _busy; 1100 } 1101 1102 protected void setBusy(boolean busy) { 1103 boolean oldBusy = _busy; 1104 _busy = busy; 1105 if (!busy && !container.isBusy()) { 1106 enableButtons(true); 1107 } 1108 if (oldBusy != busy) { 1109 firePropertyChange("Busy", Boolean.valueOf(oldBusy), Boolean.valueOf(busy)); 1110 } 1111 } 1112 1113 private int retry = 0; 1114 1115 /** 1116 * Get notification of a variable property change, specifically "busy" going 1117 * to false at the end of a programming operation. If we're in a programming 1118 * operation, we then continue it by reinvoking the nextRead/writePane 1119 * operation. 1120 * 1121 * @param e the event to respond to 1122 */ 1123 @Override 1124 public void propertyChange(java.beans.PropertyChangeEvent e) { 1125 // check for the right event & condition 1126 if (_programmingVar == null && _programmingCV == null ) { 1127 log.warn("unexpected propertChange: {}", e); 1128 return; 1129 } else if (log.isDebugEnabled()) { 1130 log.debug("property changed: {} new value: {}", e.getPropertyName(), e.getNewValue()); 1131 } 1132 1133 // find the right way to handle this 1134 if (e.getSource() == _programmingVar 1135 && e.getPropertyName().equals("Busy") 1136 && e.getNewValue().equals(Boolean.FALSE)) { 1137 if (_programmingVar.getState() == AbstractValue.ValueState.UNKNOWN) { 1138 if (retry == 0) { 1139 varListIndex--; 1140 retry++; 1141 if (_read) { 1142 _programmingVar.setToRead(true); // set the variable 1143 // to read again. 1144 } else { 1145 _programmingVar.setToWrite(true); // set the variable 1146 // to attempt another 1147 // write. 1148 } 1149 } else { 1150 retry = 0; 1151 } 1152 } 1153 replyWhileProgrammingVar(); 1154 } else if (e.getSource() == _programmingCV 1155 && e.getPropertyName().equals("Busy") 1156 && e.getNewValue().equals(Boolean.FALSE)) { 1157 1158 // there's no -- operator on the HashSet Iterator we're 1159 // using for the CV list, so we don't do individual retries 1160 // now. 1161 //if (_programmingCV.getState() == CvValue.UNKNOWN) { 1162 // if (retry == 0) { 1163 // cvListIndex--; 1164 // retry++; 1165 // } else { 1166 // retry = 0; 1167 // } 1168 //} 1169 replyWhileProgrammingCV(); 1170 } else { 1171 if (log.isDebugEnabled() && e.getPropertyName().equals("Busy")) { 1172 log.debug("ignoring change of Busy {} {}", e.getNewValue(), e.getNewValue().equals(Boolean.FALSE)); 1173 } 1174 } 1175 } 1176 1177 public void replyWhileProgrammingVar() { 1178 log.debug("correct event for programming variable, restart operation"); 1179 // remove existing listener 1180 _programmingVar.removePropertyChangeListener(this); 1181 _programmingVar = null; 1182 // restart the operation 1183 restartProgramming(); 1184 } 1185 1186 public void replyWhileProgrammingCV() { 1187 log.debug("correct event for programming CV, restart operation"); 1188 // remove existing listener 1189 _programmingCV.removePropertyChangeListener(this); 1190 _programmingCV = null; 1191 // restart the operation 1192 restartProgramming(); 1193 } 1194 1195 void restartProgramming() { 1196 log.debug("start restartProgramming"); 1197 if (_read && readChangesButton.isSelected()) { 1198 nextRead(); 1199 } else if (_read && readAllButton.isSelected()) { 1200 nextRead(); 1201 } else if (_read && confirmChangesButton.isSelected()) { 1202 nextConfirm(); 1203 } else if (_read && confirmAllButton.isSelected()) { 1204 nextConfirm(); 1205 } else if (writeChangesButton.isSelected()) { 1206 nextWrite(); // was writePaneChanges 1207 } else if (writeAllButton.isSelected()) { 1208 nextWrite(); 1209 } else { 1210 log.debug("No operation to restart"); 1211 if (isBusy()) { 1212 container.paneFinished(); 1213 setBusy(false); 1214 } 1215 } 1216 log.debug("end restartProgramming"); 1217 } 1218 1219 protected void stopProgramming() { 1220 log.debug("start stopProgramming"); 1221 setToRead(false, false); 1222 setToWrite(false, false); 1223 varListIndex = varList.size(); 1224 1225 cvListIterator = null; 1226 log.debug("end stopProgramming"); 1227 } 1228 1229 /** 1230 * Create a new group from the JDOM group Element 1231 * 1232 * @param element element containing group contents 1233 * @param showStdName show the name following the rules for the 1234 * <em>nameFmt</em> element 1235 * @param modelElem element containing the decoder model 1236 * @return a panel containing the group 1237 */ 1238 protected JPanel newGroup(Element element, boolean showStdName, Element modelElem) { 1239 1240 // create a panel to add as a new column or row 1241 final JPanel c = new JPanel(); 1242 panelList.add(c); 1243 GridBagLayout g = new GridBagLayout(); 1244 GridBagConstraints cs = new GridBagConstraints(); 1245 c.setLayout(g); 1246 1247 // handle include/exclude 1248 if (!PaneProgFrame.isIncludedFE(element, modelElem, rosterEntry, "", "")) { 1249 return c; 1250 } 1251 1252 // handle the xml definition 1253 // for all elements in the column or row 1254 List<Element> elemList = element.getChildren(); 1255 log.trace("newColumn starting with {} elements", elemList.size()); 1256 for (Element e : elemList) { 1257 1258 String name = e.getName(); 1259 log.trace("newGroup processing {} element", name); 1260 // decode the type 1261 if (name.equals("display")) { // its a variable 1262 // load the variable 1263 newVariable(e, c, g, cs, showStdName); 1264 } else if (name.equals("separator")) { // its a separator 1265 JSeparator j = new JSeparator(SwingConstants.HORIZONTAL); 1266 cs.fill = GridBagConstraints.BOTH; 1267 cs.gridwidth = GridBagConstraints.REMAINDER; 1268 g.setConstraints(j, cs); 1269 c.add(j); 1270 cs.gridwidth = 1; 1271 } else if (name.equals("label")) { 1272 cs.gridwidth = GridBagConstraints.REMAINDER; 1273 makeLabel(e, c, g, cs); 1274 } else if (name.equals("soundlabel")) { 1275 cs.gridwidth = GridBagConstraints.REMAINDER; 1276 makeSoundLabel(e, c, g, cs); 1277 } else if (name.equals("cvtable")) { 1278 makeCvTable(cs, g, c); 1279 } else if (name.equals("fnmapping")) { 1280 pickFnMapPanel(c, g, cs, modelElem); 1281 } else if (name.equals("dccaddress")) { 1282 JPanel l = addDccAddressPanel(e); 1283 if (l.getComponentCount() > 0) { 1284 cs.gridwidth = GridBagConstraints.REMAINDER; 1285 g.setConstraints(l, cs); 1286 c.add(l); 1287 cs.gridwidth = 1; 1288 } 1289 } else if (name.equals("column")) { 1290 // nested "column" elements ... 1291 cs.gridheight = GridBagConstraints.REMAINDER; 1292 JPanel l = newColumn(e, showStdName, modelElem); 1293 if (l.getComponentCount() > 0) { 1294 panelList.add(l); 1295 g.setConstraints(l, cs); 1296 c.add(l); 1297 cs.gridheight = 1; 1298 } 1299 } else if (name.equals("row")) { 1300 // nested "row" elements ... 1301 cs.gridwidth = GridBagConstraints.REMAINDER; 1302 JPanel l = newRow(e, showStdName, modelElem); 1303 if (l.getComponentCount() > 0) { 1304 panelList.add(l); 1305 g.setConstraints(l, cs); 1306 c.add(l); 1307 cs.gridwidth = 1; 1308 } 1309 } else if (name.equals("grid")) { 1310 // nested "grid" elements ... 1311 cs.gridwidth = GridBagConstraints.REMAINDER; 1312 JPanel l = newGrid(e, showStdName, modelElem); 1313 if (l.getComponentCount() > 0) { 1314 panelList.add(l); 1315 g.setConstraints(l, cs); 1316 c.add(l); 1317 cs.gridwidth = 1; 1318 } 1319 } else if (name.equals("group")) { 1320 // nested "group" elements ... 1321 JPanel l = newGroup(e, showStdName, modelElem); 1322 if (l.getComponentCount() > 0) { 1323 panelList.add(l); 1324 g.setConstraints(l, cs); 1325 c.add(l); 1326 } 1327 } else if (!name.equals("qualifier")) { // its a mistake 1328 log.error("No code to handle element of type {} in newColumn", e.getName()); 1329 } 1330 } 1331 // add glue to the bottom to allow resize 1332 if (c.getComponentCount() > 0) { 1333 c.add(Box.createVerticalGlue()); 1334 } 1335 1336 // handle qualification if any 1337 QualifierAdder qa = new QualifierAdder() { 1338 @Override 1339 protected Qualifier createQualifier(VariableValue var, String relation, String value) { 1340 return new JComponentQualifier(c, var, Integer.parseInt(value), relation); 1341 } 1342 1343 @Override 1344 protected void addListener(java.beans.PropertyChangeListener qc) { 1345 c.addPropertyChangeListener(qc); 1346 } 1347 }; 1348 1349 qa.processModifierElements(element, _varModel); 1350 return c; 1351 } 1352 1353 /** 1354 * Create a new grid group from the JDOM group Element. 1355 * 1356 * @param element element containing group contents 1357 * @param c the panel to create the grid in 1358 * @param g the layout manager for the panel 1359 * @param globs properties to configure g 1360 * @param showStdName show the name following the rules for the 1361 * <em>nameFmt</em> element 1362 * @param modelElem element containing the decoder model 1363 */ 1364 protected void newGridGroup(Element element, final JPanel c, GridBagLayout g, GridGlobals globs, boolean showStdName, Element modelElem) { 1365 1366 // handle include/exclude 1367 if (!PaneProgFrame.isIncludedFE(element, modelElem, rosterEntry, "", "")) { 1368 return; 1369 } 1370 1371 // handle the xml definition 1372 // for all elements in the column or row 1373 List<Element> elemList = element.getChildren(); 1374 log.trace("newColumn starting with {} elements", elemList.size()); 1375 for (Element e : elemList) { 1376 1377 String name = e.getName(); 1378 log.trace("newGroup processing {} element", name); 1379 // decode the type 1380 if (name.equals("griditem")) { 1381 final JPanel l = newGridItem(e, showStdName, modelElem, globs); 1382 if (l.getComponentCount() > 0) { 1383 panelList.add(l); 1384 g.setConstraints(l, globs.gridConstraints); 1385 c.add(l); 1386 // globs.gridConstraints.gridwidth = 1; 1387 // handle qualification if any 1388 QualifierAdder qa = new QualifierAdder() { 1389 @Override 1390 protected Qualifier createQualifier(VariableValue var, String relation, String value) { 1391 return new JComponentQualifier(l, var, Integer.parseInt(value), relation); 1392 } 1393 1394 @Override 1395 protected void addListener(java.beans.PropertyChangeListener qc) { 1396 l.addPropertyChangeListener(qc); 1397 } 1398 }; 1399 1400 qa.processModifierElements(e, _varModel); 1401 } 1402 } else if (name.equals("group")) { 1403 // nested "group" elements ... 1404 newGridGroup(e, c, g, globs, showStdName, modelElem); 1405 } else if (!name.equals("qualifier")) { // its a mistake 1406 log.error("No code to handle element of type {} in newColumn", e.getName()); 1407 } 1408 } 1409 // add glue to the bottom to allow resize 1410// if (c.getComponentCount() > 0) { 1411// c.add(Box.createVerticalGlue()); 1412// } 1413 1414 } 1415 1416 /** 1417 * Create a single column from the JDOM column Element. 1418 * 1419 * @param element element containing column contents 1420 * @param showStdName show the name following the rules for the 1421 * <em>nameFmt</em> element 1422 * @param modelElem element containing the decoder model 1423 * @return a panel containing the group 1424 */ 1425 public JPanel newColumn(Element element, boolean showStdName, Element modelElem) { 1426 1427 // create a panel to add as a new column or row 1428 final JPanel c = new JPanel(); 1429 panelList.add(c); 1430 GridBagLayout g = new GridBagLayout(); 1431 GridBagConstraints cs = new GridBagConstraints(); 1432 c.setLayout(g); 1433 1434 // handle the xml definition 1435 // for all elements in the column or row 1436 List<Element> elemList = element.getChildren(); 1437 log.trace("newColumn starting with {} elements", elemList.size()); 1438 for (Element value : elemList) { 1439 1440 // update the grid position 1441 cs.gridy++; 1442 cs.gridx = 0; 1443 1444 String name = value.getName(); 1445 log.trace("newColumn processing {} element", name); 1446 // decode the type 1447 if (name.equals("display")) { // it's a variable 1448 // load the variable 1449 newVariable(value, c, g, cs, showStdName); 1450 } else if (name.equals("separator")) { // its a separator 1451 JSeparator j = new JSeparator(SwingConstants.HORIZONTAL); 1452 cs.fill = GridBagConstraints.BOTH; 1453 cs.gridwidth = GridBagConstraints.REMAINDER; 1454 g.setConstraints(j, cs); 1455 c.add(j); 1456 cs.gridwidth = 1; 1457 } else if (name.equals("label")) { 1458 cs.gridwidth = GridBagConstraints.REMAINDER; 1459 makeLabel(value, c, g, cs); 1460 } else if (name.equals("soundlabel")) { 1461 cs.gridwidth = GridBagConstraints.REMAINDER; 1462 makeSoundLabel(value, c, g, cs); 1463 } else if (name.equals("cvtable")) { 1464 makeCvTable(cs, g, c); 1465 } else if (name.equals("fnmapping")) { 1466 pickFnMapPanel(c, g, cs, modelElem); 1467 } else if (name.equals("dccaddress")) { 1468 JPanel l = addDccAddressPanel(value); 1469 if (l.getComponentCount() > 0) { 1470 cs.gridwidth = GridBagConstraints.REMAINDER; 1471 g.setConstraints(l, cs); 1472 c.add(l); 1473 cs.gridwidth = 1; 1474 } 1475 } else if (name.equals("column")) { 1476 // nested "column" elements ... 1477 cs.gridheight = GridBagConstraints.REMAINDER; 1478 JPanel l = newColumn(value, showStdName, modelElem); 1479 if (l.getComponentCount() > 0) { 1480 panelList.add(l); 1481 g.setConstraints(l, cs); 1482 c.add(l); 1483 cs.gridheight = 1; 1484 } 1485 } else if (name.equals("row")) { 1486 // nested "row" elements ... 1487 cs.gridwidth = GridBagConstraints.REMAINDER; 1488 JPanel l = newRow(value, showStdName, modelElem); 1489 if (l.getComponentCount() > 0) { 1490 panelList.add(l); 1491 g.setConstraints(l, cs); 1492 c.add(l); 1493 cs.gridwidth = 1; 1494 } 1495 } else if (name.equals("grid")) { 1496 // nested "grid" elements ... 1497 cs.gridwidth = GridBagConstraints.REMAINDER; 1498 JPanel l = newGrid(value, showStdName, modelElem); 1499 if (l.getComponentCount() > 0) { 1500 panelList.add(l); 1501 g.setConstraints(l, cs); 1502 c.add(l); 1503 cs.gridwidth = 1; 1504 } 1505 } else if (name.equals("group")) { 1506 // nested "group" elements ... 1507 JPanel l = newGroup(value, showStdName, modelElem); 1508 if (l.getComponentCount() > 0) { 1509 panelList.add(l); 1510 g.setConstraints(l, cs); 1511 c.add(l); 1512 } 1513 } else if (!name.equals("qualifier")) { // its a mistake 1514 log.error("No code to handle element of type {} in newColumn", value.getName()); 1515 } 1516 } 1517 // add glue to the bottom to allow resize 1518 if (c.getComponentCount() > 0) { 1519 c.add(Box.createVerticalGlue()); 1520 } 1521 1522 // handle qualification if any 1523 QualifierAdder qa = new QualifierAdder() { 1524 @Override 1525 protected Qualifier createQualifier(VariableValue var, String relation, String value) { 1526 return new JComponentQualifier(c, var, Integer.parseInt(value), relation); 1527 } 1528 1529 @Override 1530 protected void addListener(java.beans.PropertyChangeListener qc) { 1531 c.addPropertyChangeListener(qc); 1532 } 1533 }; 1534 1535 qa.processModifierElements(element, _varModel); 1536 return c; 1537 } 1538 1539 /** 1540 * Create a single row from the JDOM column Element 1541 * 1542 * @param element element containing row contents 1543 * @param showStdName show the name following the rules for the 1544 * <em>nameFmt</em> element 1545 * @param modelElem element containing the decoder model 1546 * @return a panel containing the group 1547 */ 1548 public JPanel newRow(Element element, boolean showStdName, Element modelElem) { 1549 1550 // create a panel to add as a new column or row 1551 final JPanel c = new JPanel(); 1552 panelList.add(c); 1553 GridBagLayout g = new GridBagLayout(); 1554 GridBagConstraints cs = new GridBagConstraints(); 1555 c.setLayout(g); 1556 1557 // handle the xml definition 1558 // for all elements in the column or row 1559 List<Element> elemList = element.getChildren(); 1560 log.trace("newRow starting with {} elements", elemList.size()); 1561 for (Element value : elemList) { 1562 1563 // update the grid position 1564 cs.gridy = 0; 1565 cs.gridx++; 1566 1567 String name = value.getName(); 1568 log.trace("newRow processing {} element", name); 1569 // decode the type 1570 if (name.equals("display")) { // its a variable 1571 // load the variable 1572 newVariable(value, c, g, cs, showStdName); 1573 } else if (name.equals("separator")) { // its a separator 1574 JSeparator j = new JSeparator(SwingConstants.VERTICAL); 1575 cs.fill = GridBagConstraints.BOTH; 1576 cs.gridheight = GridBagConstraints.REMAINDER; 1577 g.setConstraints(j, cs); 1578 c.add(j); 1579 cs.fill = GridBagConstraints.NONE; 1580 cs.gridheight = 1; 1581 } else if (name.equals("label")) { 1582 cs.gridheight = GridBagConstraints.REMAINDER; 1583 makeLabel(value, c, g, cs); 1584 } else if (name.equals("soundlabel")) { 1585 cs.gridheight = GridBagConstraints.REMAINDER; 1586 makeSoundLabel(value, c, g, cs); 1587 } else if (name.equals("cvtable")) { 1588 makeCvTable(cs, g, c); 1589 } else if (name.equals("fnmapping")) { 1590 pickFnMapPanel(c, g, cs, modelElem); 1591 } else if (name.equals("dccaddress")) { 1592 JPanel l = addDccAddressPanel(value); 1593 if (l.getComponentCount() > 0) { 1594 cs.gridheight = GridBagConstraints.REMAINDER; 1595 g.setConstraints(l, cs); 1596 c.add(l); 1597 cs.gridheight = 1; 1598 } 1599 } else if (name.equals("column")) { 1600 // nested "column" elements ... 1601 cs.gridheight = GridBagConstraints.REMAINDER; 1602 JPanel l = newColumn(value, showStdName, modelElem); 1603 if (l.getComponentCount() > 0) { 1604 panelList.add(l); 1605 g.setConstraints(l, cs); 1606 c.add(l); 1607 cs.gridheight = 1; 1608 } 1609 } else if (name.equals("row")) { 1610 // nested "row" elements ... 1611 cs.gridwidth = GridBagConstraints.REMAINDER; 1612 JPanel l = newRow(value, showStdName, modelElem); 1613 if (l.getComponentCount() > 0) { 1614 panelList.add(l); 1615 g.setConstraints(l, cs); 1616 c.add(l); 1617 cs.gridwidth = 1; 1618 } 1619 } else if (name.equals("grid")) { 1620 // nested "grid" elements ... 1621 cs.gridwidth = GridBagConstraints.REMAINDER; 1622 JPanel l = newGrid(value, showStdName, modelElem); 1623 if (l.getComponentCount() > 0) { 1624 panelList.add(l); 1625 g.setConstraints(l, cs); 1626 c.add(l); 1627 cs.gridwidth = 1; 1628 } 1629 } else if (name.equals("group")) { 1630 // nested "group" elements ... 1631 JPanel l = newGroup(value, showStdName, modelElem); 1632 if (l.getComponentCount() > 0) { 1633 panelList.add(l); 1634 g.setConstraints(l, cs); 1635 c.add(l); 1636 } 1637 } else if (!name.equals("qualifier")) { // its a mistake 1638 log.error("No code to handle element of type {} in newRow", value.getName()); 1639 } 1640 } 1641 // add glue to the bottom to allow resize 1642 if (c.getComponentCount() > 0) { 1643 c.add(Box.createVerticalGlue()); 1644 } 1645 1646 // handle qualification if any 1647 QualifierAdder qa = new QualifierAdder() { 1648 @Override 1649 protected Qualifier createQualifier(VariableValue var, String relation, String value) { 1650 return new JComponentQualifier(c, var, Integer.parseInt(value), relation); 1651 } 1652 1653 @Override 1654 protected void addListener(java.beans.PropertyChangeListener qc) { 1655 c.addPropertyChangeListener(qc); 1656 } 1657 }; 1658 1659 qa.processModifierElements(element, _varModel); 1660 return c; 1661 } 1662 1663 /** 1664 * Create a grid from the JDOM Element. 1665 * 1666 * @param element element containing group contents 1667 * @param showStdName show the name following the rules for the 1668 * <em>nameFmt</em> element 1669 * @param modelElem element containing the decoder model 1670 * @return a panel containing the group 1671 */ 1672 public JPanel newGrid(Element element, boolean showStdName, Element modelElem) { 1673 1674 // create a panel to add as a new grid 1675 final JPanel c = new JPanel(); 1676 panelList.add(c); 1677 GridBagLayout g = new GridBagLayout(); 1678 c.setLayout(g); 1679 1680 GridGlobals globs = new GridGlobals(); 1681 1682 // handle the xml definition 1683 // for all elements in the grid 1684 List<Element> elemList = element.getChildren(); 1685 globs.gridAttList = element.getAttributes(); // get grid-level attributes 1686 log.trace("newGrid starting with {} elements", elemList.size()); 1687 for (Element value : elemList) { 1688 globs.gridConstraints = new GridBagConstraints(); 1689 String name = value.getName(); 1690 log.trace("newGrid processing {} element", name); 1691 // decode the type 1692 if (name.equals("griditem")) { 1693 JPanel l = newGridItem(value, showStdName, modelElem, globs); 1694 if (l.getComponentCount() > 0) { 1695 panelList.add(l); 1696 g.setConstraints(l, globs.gridConstraints); 1697 c.add(l); 1698 // globs.gridConstraints.gridwidth = 1; 1699 } 1700 } else if (name.equals("group")) { 1701 // nested "group" elements ... 1702 newGridGroup(value, c, g, globs, showStdName, modelElem); 1703 } else if (!name.equals("qualifier")) { // its a mistake 1704 log.error("No code to handle element of type {} in newGrid", value.getName()); 1705 } 1706 } 1707 1708 // add glue to the bottom to allow resize 1709 if (c.getComponentCount() > 0) { 1710 c.add(Box.createVerticalGlue()); 1711 } 1712 1713 // handle qualification if any 1714 QualifierAdder qa = new QualifierAdder() { 1715 @Override 1716 protected Qualifier createQualifier(VariableValue var, String relation, String value) { 1717 return new JComponentQualifier(c, var, Integer.parseInt(value), relation); 1718 } 1719 1720 @Override 1721 protected void addListener(java.beans.PropertyChangeListener qc) { 1722 c.addPropertyChangeListener(qc); 1723 } 1724 }; 1725 1726 qa.processModifierElements(element, _varModel); 1727 return c; 1728 } 1729 1730 protected static class GridGlobals { 1731 1732 public int gridxCurrent = -1; 1733 public int gridyCurrent = -1; 1734 public List<Attribute> gridAttList; 1735 public GridBagConstraints gridConstraints; 1736 } 1737 1738 /** 1739 * Create a grid item from the JDOM Element 1740 * 1741 * @param element element containing grid item contents 1742 * @param showStdName show the name following the rules for the 1743 * <em>nameFmt</em> element 1744 * @param modelElem element containing the decoder model 1745 * @param globs properties to configure the layout 1746 * @return a panel containing the group 1747 */ 1748 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("DP_DO_INSIDE_DO_PRIVILEGED") // setAccessible() 1749 public JPanel newGridItem(Element element, boolean showStdName, Element modelElem, GridGlobals globs) { 1750 1751 List<Attribute> itemAttList = element.getAttributes(); // get item-level attributes 1752 List<Attribute> attList = new ArrayList<>(globs.gridAttList); 1753 attList.addAll(itemAttList); // merge grid and item-level attributes 1754// log.info("New gridtiem -----------------------------------------------"); 1755// log.info("Attribute list:"+attList); 1756 attList.add(new Attribute(LAST_GRIDX, "")); 1757 attList.add(new Attribute(LAST_GRIDY, "")); 1758// log.info("Updated Attribute list:"+attList); 1759// Attribute ax = attList.get(attList.size()-2); 1760// Attribute ay = attList.get(attList.size()-1); 1761// log.info("ax="+ax+";ay="+ay); 1762// log.info("Previous gridxCurrent="+globs.gridxCurrent+";gridyCurrent="+globs.gridyCurrent); 1763 for (int j = 0; j < attList.size(); j++) { 1764 Attribute attrib = attList.get(j); 1765 String attribName = attrib.getName(); 1766 String attribRawValue = attrib.getValue(); 1767 Field constraint; 1768 String constraintType; 1769 // make sure we only process the last gridx or gridy attribute in the list 1770 if (attribName.equals("gridx")) { 1771 Attribute a = new Attribute(LAST_GRIDX, attribRawValue); 1772 attList.set(attList.size() - 2, a); 1773// log.info("Moved & Updated Attribute list:"+attList); 1774 continue; //. don't process now 1775 } 1776 if (attribName.equals("gridy")) { 1777 Attribute a = new Attribute(LAST_GRIDY, attribRawValue); 1778 attList.set(attList.size() - 1, a); 1779// log.info("Moved & Updated Attribute list:"+attList); 1780 continue; //. don't process now 1781 } 1782 if (attribName.equals(LAST_GRIDX)) { // we must be at end of original list, restore last gridx 1783 attribName = "gridx"; 1784 if (attribRawValue.equals("")) { // don't process blank (unused) 1785 continue; 1786 } 1787 } 1788 if (attribName.equals(LAST_GRIDY)) { // we must be at end of original list, restore last gridy 1789 attribName = "gridy"; 1790 if (attribRawValue.equals("")) { // don't process blank (unused) 1791 continue; 1792 } 1793 } 1794 if ((attribName.equals("gridx") || attribName.equals("gridy")) && attribRawValue.equals("RELATIVE")) { 1795 attribRawValue = "NEXT"; // NEXT is a synonym for RELATIVE 1796 } 1797 if (attribName.equals("gridx") && attribRawValue.equals("CURRENT")) { 1798 attribRawValue = String.valueOf(Math.max(0, globs.gridxCurrent)); 1799 } 1800 if (attribName.equals("gridy") && attribRawValue.equals("CURRENT")) { 1801 attribRawValue = String.valueOf(Math.max(0, globs.gridyCurrent)); 1802 } 1803 if (attribName.equals("gridx") && attribRawValue.equals("NEXT")) { 1804 attribRawValue = String.valueOf(++globs.gridxCurrent); 1805 } 1806 if (attribName.equals("gridy") && attribRawValue.equals("NEXT")) { 1807 attribRawValue = String.valueOf(++globs.gridyCurrent); 1808 } 1809// log.info("attribName="+attribName+";attribRawValue="+attribRawValue); 1810 try { 1811 constraint = globs.gridConstraints.getClass().getDeclaredField(attribName); 1812 constraintType = constraint.getType().toString(); 1813 constraint.setAccessible(true); 1814 } catch (NoSuchFieldException ex) { 1815 log.error("Unrecognised attribute \"{}\", skipping", attribName); 1816 continue; 1817 } 1818 switch (constraintType) { 1819 case "int": { 1820 int attribValue; 1821 try { 1822 attribValue = Integer.parseInt(attribRawValue); 1823 constraint.set(globs.gridConstraints, attribValue); 1824 } catch (IllegalAccessException ey) { 1825 log.error("Unable to set constraint \"{}. IllegalAccessException error thrown.", attribName); 1826 } catch (NumberFormatException ex) { 1827 try { 1828 Field constant = globs.gridConstraints.getClass().getDeclaredField(attribRawValue); 1829 constant.setAccessible(true); 1830 attribValue = (Integer) GridBagConstraints.class.getField(attribRawValue).get(constant); 1831 constraint.set(globs.gridConstraints, attribValue); 1832 } catch (NoSuchFieldException ey) { 1833 log.error("Invalid value \"{}\" for attribute \"{}\"", attribRawValue, attribName); 1834 } catch (IllegalAccessException ey) { 1835 log.error("Unable to set constraint \"{}. IllegalAccessException error thrown.", attribName); 1836 } 1837 } 1838 break; 1839 } 1840 case "double": { 1841 double attribValue; 1842 try { 1843 attribValue = Double.parseDouble(attribRawValue); 1844 constraint.set(globs.gridConstraints, attribValue); 1845 } catch (IllegalAccessException ey) { 1846 log.error("Unable to set constraint \"{}. IllegalAccessException error thrown.", attribName); 1847 } catch (NumberFormatException ex) { 1848 log.error("Invalid value \"{}\" for attribute \"{}\"", attribRawValue, attribName); 1849 } 1850 break; 1851 } 1852 case "class java.awt.Insets": 1853 try { 1854 String[] insetStrings = attribRawValue.split(","); 1855 if (insetStrings.length == 4) { 1856 Insets attribValue = new Insets(Integer.parseInt(insetStrings[0]), Integer.parseInt(insetStrings[1]), Integer.parseInt(insetStrings[2]), Integer.parseInt(insetStrings[3])); 1857 constraint.set(globs.gridConstraints, attribValue); 1858 } else { 1859 log.error("Invalid value \"{}\" for attribute \"{}\"", attribRawValue, attribName); 1860 log.error("Value should be four integers of the form \"top,left,bottom,right\""); 1861 } 1862 } catch (IllegalAccessException ey) { 1863 log.error("Unable to set constraint \"{}. IllegalAccessException error thrown.", attribName); 1864 } catch (NumberFormatException ex) { 1865 log.error("Invalid value \"{}\" for attribute \"{}\"", attribRawValue, attribName); 1866 log.error("Value should be four integers of the form \"top,left,bottom,right\""); 1867 } 1868 break; 1869 default: 1870 log.error("Required \"{}\" handler for attribute \"{}\" not defined in JMRI code", constraintType, attribName); 1871 log.error("Please file a JMRI bug report at https://sourceforge.net/p/jmri/bugs/new/"); 1872 break; 1873 } 1874 } 1875// log.info("Updated globs.GridBagConstraints.gridx="+globs.gridConstraints.gridx+";globs.GridBagConstraints.gridy="+globs.gridConstraints.gridy); 1876 1877 // create a panel to add as a new grid item 1878 final JPanel c = new JPanel(); 1879 panelList.add(c); 1880 GridBagLayout g = new GridBagLayout(); 1881 GridBagConstraints cs = new GridBagConstraints(); 1882 c.setLayout(g); 1883 1884 // handle the xml definition 1885 // for all elements in the grid item 1886 List<Element> elemList = element.getChildren(); 1887 log.trace("newGridItem starting with {} elements", elemList.size()); 1888 for (Element value : elemList) { 1889 1890 // update the grid position 1891 cs.gridy = 0; 1892 cs.gridx++; 1893 1894 String name = value.getName(); 1895 log.trace("newGridItem processing {} element", name); 1896 // decode the type 1897 if (name.equals("display")) { // its a variable 1898 // load the variable 1899 newVariable(value, c, g, cs, showStdName); 1900 } else if (name.equals("separator")) { // its a separator 1901 JSeparator j = new JSeparator(SwingConstants.VERTICAL); 1902 cs.fill = GridBagConstraints.BOTH; 1903 cs.gridheight = GridBagConstraints.REMAINDER; 1904 g.setConstraints(j, cs); 1905 c.add(j); 1906 cs.fill = GridBagConstraints.NONE; 1907 cs.gridheight = 1; 1908 } else if (name.equals("label")) { 1909 cs.gridheight = GridBagConstraints.REMAINDER; 1910 makeLabel(value, c, g, cs); 1911 } else if (name.equals("soundlabel")) { 1912 cs.gridheight = GridBagConstraints.REMAINDER; 1913 makeSoundLabel(value, c, g, cs); 1914 } else if (name.equals("cvtable")) { 1915 makeCvTable(cs, g, c); 1916 } else if (name.equals("fnmapping")) { 1917 pickFnMapPanel(c, g, cs, modelElem); 1918 } else if (name.equals("dccaddress")) { 1919 JPanel l = addDccAddressPanel(value); 1920 if (l.getComponentCount() > 0) { 1921 cs.gridheight = GridBagConstraints.REMAINDER; 1922 g.setConstraints(l, cs); 1923 c.add(l); 1924 cs.gridheight = 1; 1925 } 1926 } else if (name.equals("column")) { 1927 // nested "column" elements ... 1928 cs.gridheight = GridBagConstraints.REMAINDER; 1929 JPanel l = newColumn(value, showStdName, modelElem); 1930 if (l.getComponentCount() > 0) { 1931 panelList.add(l); 1932 g.setConstraints(l, cs); 1933 c.add(l); 1934 cs.gridheight = 1; 1935 } 1936 } else if (name.equals("row")) { 1937 // nested "row" elements ... 1938 cs.gridwidth = GridBagConstraints.REMAINDER; 1939 JPanel l = newRow(value, showStdName, modelElem); 1940 if (l.getComponentCount() > 0) { 1941 panelList.add(l); 1942 g.setConstraints(l, cs); 1943 c.add(l); 1944 cs.gridwidth = 1; 1945 } 1946 } else if (name.equals("grid")) { 1947 // nested "grid" elements ... 1948 cs.gridwidth = GridBagConstraints.REMAINDER; 1949 JPanel l = newGrid(value, showStdName, modelElem); 1950 if (l.getComponentCount() > 0) { 1951 panelList.add(l); 1952 g.setConstraints(l, cs); 1953 c.add(l); 1954 cs.gridwidth = 1; 1955 } 1956 } else if (name.equals("group")) { 1957 // nested "group" elements ... 1958 JPanel l = newGroup(value, showStdName, modelElem); 1959 if (l.getComponentCount() > 0) { 1960 panelList.add(l); 1961 g.setConstraints(l, cs); 1962 c.add(l); 1963 } 1964 } else if (!name.equals("qualifier")) { // its a mistake 1965 log.error("No code to handle element of type {} in newGridItem", value.getName()); 1966 } 1967 } 1968 1969 globs.gridxCurrent = globs.gridConstraints.gridx; 1970 globs.gridyCurrent = globs.gridConstraints.gridy; 1971// log.info("Updated gridxCurrent="+globs.gridxCurrent+";gridyCurrent="+globs.gridyCurrent); 1972 1973 // add glue to the bottom to allow resize 1974 if (c.getComponentCount() > 0) { 1975 c.add(Box.createVerticalGlue()); 1976 } 1977 1978 // handle qualification if any 1979 QualifierAdder qa = new QualifierAdder() { 1980 @Override 1981 protected Qualifier createQualifier(VariableValue var, String relation, String value) { 1982 return new JComponentQualifier(c, var, Integer.parseInt(value), relation); 1983 } 1984 1985 @Override 1986 protected void addListener(java.beans.PropertyChangeListener qc) { 1987 c.addPropertyChangeListener(qc); 1988 } 1989 }; 1990 1991 qa.processModifierElements(element, _varModel); 1992 return c; 1993 } 1994 1995 /** 1996 * Create label from Element. 1997 * 1998 * @param e element containing label contents 1999 * @param c panel to insert label into 2000 * @param g panel layout manager 2001 * @param cs constraints on layout manager 2002 */ 2003 protected void makeLabel(Element e, JPanel c, GridBagLayout g, GridBagConstraints cs) { 2004 String text = LocaleSelector.getAttribute(e, "text"); 2005 if (text == null || text.equals("")) { 2006 text = LocaleSelector.getAttribute(e, "label"); // label subelement not since 3.7.5 2007 } 2008 final JLabel l = new JLabel(text); 2009 l.setAlignmentX(1.0f); 2010 cs.fill = GridBagConstraints.BOTH; 2011 log.trace("Add label: {} cs: {} fill: {} x: {} y: {}", 2012 l.getText(), cs.gridwidth, cs.fill, cs.gridx, cs.gridy); 2013 g.setConstraints(l, cs); 2014 c.add(l); 2015 cs.fill = GridBagConstraints.NONE; 2016 cs.gridwidth = 1; 2017 cs.gridheight = 1; 2018 2019 // handle qualification if any 2020 QualifierAdder qa = new QualifierAdder() { 2021 @Override 2022 protected Qualifier createQualifier(VariableValue var, String relation, String value) { 2023 return new JComponentQualifier(l, var, Integer.parseInt(value), relation); 2024 } 2025 2026 @Override 2027 protected void addListener(java.beans.PropertyChangeListener qc) { 2028 l.addPropertyChangeListener(qc); 2029 } 2030 }; 2031 2032 qa.processModifierElements(e, _varModel); 2033 } 2034 2035 /** 2036 * Create sound label from Element. 2037 * 2038 * @param e element containing label contents 2039 * @param c panel to insert label into 2040 * @param g panel layout manager 2041 * @param cs constraints on layout manager 2042 */ 2043 protected void makeSoundLabel(Element e, JPanel c, GridBagLayout g, GridBagConstraints cs) { 2044 String labelText = rosterEntry.getSoundLabel(Integer.parseInt(Objects.requireNonNull(LocaleSelector.getAttribute(e, "num")))); 2045 final JLabel l = new JLabel(labelText); 2046 l.setAlignmentX(1.0f); 2047 cs.fill = GridBagConstraints.BOTH; 2048 if (log.isDebugEnabled()) { 2049 log.debug("Add soundlabel: {} cs: {} {} {} {}", l.getText(), cs.gridwidth, cs.fill, cs.gridx, cs.gridy); 2050 } 2051 g.setConstraints(l, cs); 2052 c.add(l); 2053 cs.fill = GridBagConstraints.NONE; 2054 cs.gridwidth = 1; 2055 cs.gridheight = 1; 2056 2057 // handle qualification if any 2058 QualifierAdder qa = new QualifierAdder() { 2059 @Override 2060 protected Qualifier createQualifier(VariableValue var, String relation, String value) { 2061 return new JComponentQualifier(l, var, Integer.parseInt(value), relation); 2062 } 2063 2064 @Override 2065 protected void addListener(java.beans.PropertyChangeListener qc) { 2066 l.addPropertyChangeListener(qc); 2067 } 2068 }; 2069 2070 qa.processModifierElements(e, _varModel); 2071 } 2072 2073 void makeCvTable(GridBagConstraints cs, GridBagLayout g, JPanel c) { 2074 log.debug("starting to build CvTable pane"); 2075 2076 TableRowSorter<TableModel> sorter = new TableRowSorter<>(_cvModel); 2077 2078 JTable cvTable = new JTable(_cvModel); 2079 2080 sorter.setComparator(CvTableModel.NUMCOLUMN, new jmri.jmrit.symbolicprog.CVNameComparator()); 2081 2082 List<RowSorter.SortKey> sortKeys = new ArrayList<>(); 2083 sortKeys.add(new RowSorter.SortKey(0, SortOrder.ASCENDING)); 2084 sorter.setSortKeys(sortKeys); 2085 2086 cvTable.setRowSorter(sorter); 2087 2088 cvTable.setDefaultRenderer(JTextField.class, new CvValueRenderer()); 2089 cvTable.setDefaultRenderer(JButton.class, new CvValueRenderer()); 2090 cvTable.setDefaultRenderer(String.class, new CvValueRenderer()); 2091 cvTable.setDefaultRenderer(Integer.class, new CvValueRenderer()); 2092 cvTable.setDefaultEditor(JTextField.class, new ValueEditor()); 2093 cvTable.setDefaultEditor(JButton.class, new ValueEditor()); 2094 cvTable.setRowHeight(new JButton("X").getPreferredSize().height); 2095 // have to shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541) 2096 // instead of forcing the columns to fill the frame (and only fill) 2097 //cvTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 2098 JScrollPane cvScroll = new JScrollPane(cvTable); 2099 cvScroll.setColumnHeaderView(cvTable.getTableHeader()); 2100 2101 cs.fill = GridBagConstraints.BOTH; 2102 cs.weighty = 2.0; 2103 cs.weightx = 0.75; 2104 g.setConstraints(cvScroll, cs); 2105 c.add(cvScroll); 2106 2107 // remember which CVs to read/write 2108 isCvTablePane = true; 2109 setCvListFromTable(); 2110 2111 _cvTable = true; 2112 log.debug("end of building CvTable pane"); 2113 } 2114 2115 void setCvListFromTable() { 2116 // remember which CVs to read/write 2117 for (int j = 0; j < _cvModel.getRowCount(); j++) { 2118 cvList.add(j); 2119 } 2120 _varModel.setButtonModeFromProgrammer(); 2121 } 2122 2123 /** 2124 * Pick an appropriate function map panel depending on model attribute. 2125 * <dl> 2126 * <dt>If attribute extFnsESU="yes":</dt> 2127 * <dd>Invoke 2128 * {@code FnMapPanelESU(VariableTableModel v, List<Integer> varsUsed, Element model)}</dd> 2129 * <dt>Otherwise:</dt> 2130 * <dd>Invoke 2131 * {@code FnMapPanel(VariableTableModel v, List<Integer> varsUsed, Element model)}</dd> 2132 * </dl> 2133 * 2134 * @param modelElem element containing model attributes 2135 * @param c panel to add function map panel to 2136 * @param g panel layout manager 2137 * @param cs constraints on layout manager 2138 */ 2139 // why does this use a different parameter order than all similar methods? 2140 void pickFnMapPanel(JPanel c, GridBagLayout g, GridBagConstraints cs, Element modelElem) { 2141 boolean extFnsESU = false; 2142 Attribute a = modelElem.getAttribute("extFnsESU"); 2143 try { 2144 if (a != null) { 2145 extFnsESU = !(a.getValue()).equalsIgnoreCase("no"); 2146 } 2147 } catch (Exception ex) { 2148 log.error("error handling decoder's extFnsESU value"); 2149 } 2150 if (extFnsESU) { 2151 FnMapPanelESU l = new FnMapPanelESU(_varModel, varList, modelElem, rosterEntry, _cvModel); 2152 fnMapListESU.add(l); // remember for deletion 2153 cs.gridwidth = GridBagConstraints.REMAINDER; 2154 g.setConstraints(l, cs); 2155 c.add(l); 2156 } else { 2157 FnMapPanel l = new FnMapPanel(_varModel, varList, modelElem); 2158 fnMapList.add(l); // remember for deletion 2159 cs.gridwidth = GridBagConstraints.REMAINDER; 2160 g.setConstraints(l, cs); 2161 c.add(l); 2162 } 2163 cs.gridwidth = 1; 2164 } 2165 2166 /** 2167 * Add the representation of a single variable. The variable is defined by a 2168 * JDOM variable Element from the XML file. 2169 * 2170 * @param var element containing variable 2171 * @param col column to insert label into 2172 * @param g panel layout manager 2173 * @param cs constraints on layout manager 2174 * @param showStdName show the name following the rules for the 2175 * <em>nameFmt</em> element 2176 */ 2177 public void newVariable(Element var, JComponent col, 2178 GridBagLayout g, GridBagConstraints cs, boolean showStdName) { 2179 2180 // get the name 2181 String name = var.getAttribute("item").getValue(); 2182 2183 // if it doesn't exist, do nothing 2184 int i = _varModel.findVarIndex(name); 2185 if (i < 0) { 2186 log.trace("Variable \"{}\" not found, omitted", name); 2187 return; 2188 } 2189// Leave here for now. Need to track pre-existing corner-case issue 2190// log.info("Entry item="+name+";cs.gridx="+cs.gridx+";cs.gridy="+cs.gridy+";cs.anchor="+cs.anchor+";cs.ipadx="+cs.ipadx); 2191 2192 // check label orientation 2193 Attribute attr; 2194 String layout = "left"; // this default is also set in the DTD 2195 if ((attr = var.getAttribute("layout")) != null && attr.getValue() != null) { 2196 layout = attr.getValue(); 2197 } 2198 2199 // load label if specified, else use name 2200 String label = name; 2201 if (!showStdName) { 2202 // get name attribute from variable, as that's the mfg name 2203 label = _varModel.getLabel(i); 2204 } 2205 String temp = LocaleSelector.getAttribute(var, "label"); 2206 if (temp != null) { 2207 label = temp; 2208 } 2209 2210 // get representation; store into the list to be programmed 2211 JComponent rep = getRepresentation(name, var); 2212 varList.add(i); 2213 2214 // create the paired label 2215 JLabel l = new WatchingLabel(label, rep); 2216 2217 int spaceWidth = getFontMetrics(l.getFont()).stringWidth(" "); 2218 2219 // now handle the four orientations 2220 // assemble v from label, rep 2221 switch (layout) { 2222 case "left": 2223 cs.anchor = GridBagConstraints.EAST; 2224 cs.ipadx = spaceWidth; 2225 g.setConstraints(l, cs); 2226 col.add(l); 2227 cs.ipadx = 0; 2228 cs.gridx++; 2229 cs.anchor = GridBagConstraints.WEST; 2230 g.setConstraints(rep, cs); 2231 col.add(rep); 2232 break; 2233// log.info("Exit item="+name+";cs.gridx="+cs.gridx+";cs.gridy="+cs.gridy+";cs.anchor="+cs.anchor+";cs.ipadx="+cs.ipadx); 2234 case "right": 2235 cs.anchor = GridBagConstraints.EAST; 2236 g.setConstraints(rep, cs); 2237 col.add(rep); 2238 cs.gridx++; 2239 cs.anchor = GridBagConstraints.WEST; 2240 cs.ipadx = spaceWidth; 2241 g.setConstraints(l, cs); 2242 col.add(l); 2243 cs.ipadx = 0; 2244 break; 2245 case "below": 2246 // variable in center of upper line 2247 cs.anchor = GridBagConstraints.CENTER; 2248 g.setConstraints(rep, cs); 2249 col.add(rep); 2250 // label aligned like others 2251 cs.gridy++; 2252 cs.anchor = GridBagConstraints.WEST; 2253 cs.ipadx = spaceWidth; 2254 g.setConstraints(l, cs); 2255 col.add(l); 2256 cs.ipadx = 0; 2257 break; 2258 case "above": 2259 // label aligned like others 2260 cs.anchor = GridBagConstraints.WEST; 2261 cs.ipadx = spaceWidth; 2262 g.setConstraints(l, cs); 2263 col.add(l); 2264 cs.ipadx = 0; 2265 // variable in center of lower line 2266 cs.gridy++; 2267 cs.anchor = GridBagConstraints.CENTER; 2268 g.setConstraints(rep, cs); 2269 col.add(rep); 2270 break; 2271 default: 2272 log.error("layout internally inconsistent: {}", layout); 2273 } 2274 } 2275 2276 /** 2277 * Get a GUI representation of a particular variable for display. 2278 * 2279 * @param name Name used to look up the Variable object 2280 * @param var XML Element which might contain a "format" attribute to be 2281 * used in the {@link VariableValue#getNewRep} call from the 2282 * Variable object; "tooltip" elements are also processed here. 2283 * @return JComponent representing this variable 2284 */ 2285 public JComponent getRepresentation(String name, Element var) { 2286 int i = _varModel.findVarIndex(name); 2287 VariableValue variable = _varModel.getVariable(i); 2288 JComponent rep = null; 2289 String format = "default"; 2290 Attribute attr; 2291 if ((attr = var.getAttribute("format")) != null && attr.getValue() != null) { 2292 format = attr.getValue(); 2293 } 2294 2295 boolean viewOnly = (var.getAttribute("viewOnly") != null && 2296 var.getAttribute("viewOnly").getValue().equals("yes")); 2297 2298 if (i >= 0) { 2299 rep = getRep(i, format); 2300 rep.setMaximumSize(rep.getPreferredSize()); 2301 // set tooltip if specified here & not overridden by defn in Variable 2302 String tip = LocaleSelector.getAttribute(var, "tooltip"); 2303 if (rep.getToolTipText() != null) { 2304 tip = rep.getToolTipText(); 2305 } 2306 rep.setToolTipText(modifyToolTipText(tip, variable)); 2307 if (viewOnly) { 2308 rep.setEnabled(false); 2309 } 2310 } 2311 return rep; 2312 } 2313 2314 /** 2315 * Takes default tool tip text, e.g. from the decoder element, and modifies 2316 * it as needed. 2317 * <p> 2318 * Intended to handle e.g. adding CV numbers to variables. 2319 * 2320 * @param start existing tool tip text 2321 * @param variable the CV 2322 * @return new tool tip text 2323 */ 2324 String modifyToolTipText(String start, VariableValue variable) { 2325 log.trace("modifyToolTipText: {}", variable.label()); 2326 // this is the place to invoke VariableValue methods to (conditionally) 2327 // add information about CVs, etc in the ToolTip text 2328 2329 // Optionally add CV numbers based on Roster Preferences setting 2330 start = CvUtil.addCvDescription(start, variable.getCvDescription(), variable.getMask()); 2331 2332 // Indicate what the command station can do 2333 // need to update this with e.g. the specific CV numbers 2334 if (_cvModel.getProgrammer() != null 2335 && !_cvModel.getProgrammer().getCanRead()) { 2336 start = StringUtil.concatTextHtmlAware(start, SymbolicProgBundle.getMessage("TipHardwareCannotRead")); 2337 } 2338 if (_cvModel.getProgrammer() != null 2339 && !_cvModel.getProgrammer().getCanWrite()) { 2340 start = StringUtil.concatTextHtmlAware(start, SymbolicProgBundle.getMessage("TipHardwareCannotWrite")); 2341 } 2342 2343 // indicate other reasons for read/write constraints 2344 if (variable.getReadOnly()) { 2345 start = StringUtil.concatTextHtmlAware(start, SymbolicProgBundle.getMessage("TipDefinedToBeReadOnly")); 2346 } 2347 if (variable.getWriteOnly()) { 2348 start = StringUtil.concatTextHtmlAware(start, SymbolicProgBundle.getMessage("TipDefinedToBeWriteOnly")); 2349 } 2350 2351 return start; 2352 } 2353 2354 JComponent getRep(int i, String format) { 2355 return (JComponent) (_varModel.getRep(i, format)); 2356 } 2357 2358 /** 2359 * list of fnMapping objects to dispose 2360 */ 2361 ArrayList<FnMapPanel> fnMapList = new ArrayList<>(); 2362 ArrayList<FnMapPanelESU> fnMapListESU = new ArrayList<>(); 2363 /** 2364 * list of JPanel objects to removeAll 2365 */ 2366 ArrayList<JPanel> panelList = new ArrayList<>(); 2367 2368 public void dispose() { 2369 log.debug("dispose"); 2370 2371 // remove components 2372 removeAll(); 2373 2374 readChangesButton.removeItemListener(l1); 2375 readAllButton.removeItemListener(l2); 2376 writeChangesButton.removeItemListener(l3); 2377 writeAllButton.removeItemListener(l4); 2378 confirmChangesButton.removeItemListener(l5); 2379 confirmAllButton.removeItemListener(l6); 2380 l1 = l2 = l3 = l4 = l5 = l6 = null; 2381 2382 if (_programmingVar != null) { 2383 _programmingVar.removePropertyChangeListener(this); 2384 } 2385 if (_programmingCV != null) { 2386 _programmingCV.removePropertyChangeListener(this); 2387 } 2388 2389 _programmingVar = null; 2390 _programmingCV = null; 2391 2392 varList.clear(); 2393 varList = null; 2394 cvList.clear(); 2395 cvList = null; 2396 2397 // dispose of any panels 2398 for (JPanel jPanel : panelList) { 2399 jPanel.removeAll(); 2400 } 2401 panelList.clear(); 2402 panelList = null; 2403 2404 // dispose of any fnMaps 2405 for (FnMapPanel fnMapPanel : fnMapList) { 2406 fnMapPanel.dispose(); 2407 } 2408 fnMapList.clear(); 2409 fnMapList = null; 2410 2411 // dispose of any fnMaps 2412 for (FnMapPanelESU fnMapPanelESU : fnMapListESU) { 2413 fnMapPanelESU.dispose(); 2414 } 2415 fnMapListESU.clear(); 2416 fnMapListESU = null; 2417 2418 readChangesButton = null; 2419 writeChangesButton = null; 2420 2421 // these are disposed elsewhere 2422 _cvModel = null; 2423 _varModel = null; 2424 } 2425 2426 /** 2427 * Check if varList and cvList, and thus the tab, is empty. 2428 * 2429 * @return true if empty 2430 */ 2431 public boolean isEmpty() { 2432 return (varList.isEmpty() && cvList.isEmpty()); 2433 } 2434 2435 public boolean includeInPrint() { 2436 return print; 2437 } 2438 2439 public void includeInPrint(boolean inc) { 2440 print = inc; 2441 } 2442 boolean print = false; 2443 2444 public void printPane(HardcopyWriter w) { 2445 // if pane is empty, don't print anything 2446 if (isEmpty()) { 2447 return; 2448 } 2449 2450 // Define column widths for name and value output. 2451 // Make col 2 slightly larger than col 1 and reduce both to allow for 2452 // extra spaces that will be added during concatenation 2453 int col1Width = w.getCharactersPerLine() / 2 - 3 - 5; 2454 int col2Width = w.getCharactersPerLine() / 2 - 3 + 5; 2455 2456 try { 2457 //Create a string of spaces the width of the first column 2458 StringBuilder spaces = new StringBuilder(); 2459 spaces.append(" ".repeat(Math.max(0, col1Width))); 2460 // start with pane name in bold 2461 String heading1 = SymbolicProgBundle.getMessage("PrintHeadingField"); 2462 String heading2 = SymbolicProgBundle.getMessage("PrintHeadingSetting"); 2463 String s; 2464 int interval = spaces.length() - heading1.length(); 2465 w.setFont(null, Font.BOLD, null); 2466 // write the section name and dividing line 2467 s = mName; 2468 w.write(s, 0, s.length()); 2469 w.writeBorders(); 2470 //Draw horizontal dividing line for each Pane section 2471 w.writeLine(w.getCurrentVPos(), 0, w.getCurrentVPos(), w.getPrintablePagesizePoints().width); 2472 s = "\n"; 2473 w.write(s, 0, s.length()); 2474 // if this isn't the raw CV section, write the column headings 2475 if (cvList.isEmpty()) { 2476 w.setFont(null, Font.BOLD + Font.ITALIC, null); 2477 s = " " + heading1 + spaces.substring(0, interval) + " " + heading2; 2478 w.write(s, 0, s.length()); 2479 w.writeBorders(); 2480 s = "\n"; 2481 w.write(s, 0, s.length()); 2482 } 2483 w.setFont(null, Font.PLAIN, null); 2484 // Define a vector to store the names of variables that have been printed 2485 // already. If they have been printed, they will be skipped. 2486 // Using a vector here since we don't know how many variables will 2487 // be printed and it allows expansion as necessary 2488 ArrayList<String> printedVariables = new ArrayList<>(10); 2489 // index over variables 2490 for (int varNum : varList) { 2491 VariableValue var = _varModel.getVariable(varNum); 2492 String name = var.label(); 2493 if (name == null) { 2494 name = var.item(); 2495 } 2496 // Check if variable has been printed. If not store it and print 2497 boolean alreadyPrinted = false; 2498 for (String printedVariable : printedVariables) { 2499 if (name.equals(printedVariable)) { 2500 alreadyPrinted = true; 2501 break; 2502 } 2503 } 2504 // If already printed, skip it. If not, store it and print 2505 if (alreadyPrinted) { 2506 continue; 2507 } 2508 printedVariables.add(name); 2509 2510 String value = var.getTextValue(); 2511 String originalName = name; 2512 String originalValue = value; 2513 name = name + " (CV" + var.getCvNum() + ")"; // NO I18N 2514 2515 // define index values for name and value substrings 2516 int nameLeftIndex = 0; 2517 int nameRightIndex = name.length(); 2518 int valueLeftIndex = 0; 2519 int valueRightIndex = value.length(); 2520 String trimmedName; 2521 String trimmedValue; 2522 2523 // Check the name length to see if it is wider than the column. 2524 // If so, split it and do the same checks for the Value 2525 // Then concatenate the name and value (or the split versions thereof) 2526 // before writing - if split, repeat until all pieces have been output 2527 while ((valueLeftIndex < value.length()) || (nameLeftIndex < name.length())) { 2528 // name split code 2529 if (name.substring(nameLeftIndex).length() > col1Width) { 2530 for (int j = 0; j < col1Width; j++) { 2531 String delimiter = name.substring(nameLeftIndex + col1Width - j - 1, nameLeftIndex + col1Width - j); 2532 if (delimiter.equals(" ") || delimiter.equals(";") || delimiter.equals(",")) { 2533 nameRightIndex = nameLeftIndex + col1Width - j; 2534 break; 2535 } 2536 } 2537 trimmedName = name.substring(nameLeftIndex, nameRightIndex); 2538 nameLeftIndex = nameRightIndex; 2539 int space = spaces.length() - trimmedName.length(); 2540 s = " " + trimmedName + spaces.substring(0, space); 2541 } else { 2542 trimmedName = name.substring(nameLeftIndex); 2543 int space = spaces.length() - trimmedName.length(); 2544 s = " " + trimmedName + spaces.substring(0, space); 2545 name = ""; 2546 nameLeftIndex = 0; 2547 } 2548 // value split code 2549 if (value.substring(valueLeftIndex).length() > col2Width) { 2550 for (int j = 0; j < col2Width; j++) { 2551 String delimiter = value.substring(valueLeftIndex + col2Width - j - 1, valueLeftIndex + col2Width - j); 2552 if (delimiter.equals(" ") || delimiter.equals(";") || delimiter.equals(",")) { 2553 valueRightIndex = valueLeftIndex + col2Width - j; 2554 break; 2555 } 2556 } 2557 trimmedValue = value.substring(valueLeftIndex, valueRightIndex); 2558 valueLeftIndex = valueRightIndex; 2559 s = s + " " + trimmedValue; 2560 } else { 2561 trimmedValue = value.substring(valueLeftIndex); 2562 s = s + " " + trimmedValue; 2563 valueLeftIndex = 0; 2564 value = ""; 2565 } 2566 w.write(s, 0, s.length()); 2567 w.writeBorders(); 2568 s = "\n"; 2569 w.write(s, 0, s.length()); 2570 } 2571 // Check for a Speed Table output and create a graphic display. 2572 // Java 1.5 has a known bug, #6328248, that prevents printing of progress 2573 // bars using old style printing classes. It results in blank bars on Windows, 2574 // but hangs Macs. The version check is a workaround. 2575 float v = Float.parseFloat(System.getProperty("java.version").substring(0, 3)); 2576 if (originalName.equals("Speed Table") && v < 1.5) { 2577 // set the height of the speed table graph in lines 2578 int speedFrameLineHeight = 11; 2579 s = "\n"; 2580 2581 // check that there is enough room on the page; if not, 2582 // space down the rest of the page. 2583 // don't use page break because we want the table borders to be written 2584 // to the bottom of the page 2585 Dimension pagesize = w.getPrintablePagesizePoints(); 2586 int here = w.getCurrentVPos(); 2587 w.writeLine(here, 0, pagesize.height, 0); 2588 w.writeLine(here, pagesize.width, pagesize.height, pagesize.width); 2589 w.pageBreak(); 2590 2591 // Now that there is page space, create the window to hold the graphic speed table 2592 JWindow speedWindow = new JWindow(); 2593 // Window size as wide as possible to allow for largest type size 2594 speedWindow.setSize(512, 165); 2595 speedWindow.getContentPane().setBackground(Color.white); 2596 speedWindow.getContentPane().setLayout(null); 2597 // in preparation for display, extract the speed table values into an array 2598 StringTokenizer valueTokens = new StringTokenizer(originalValue, ",", false); 2599 int[] speedVals = new int[28]; 2600 int k = 0; 2601 while (valueTokens.hasMoreTokens()) { 2602 speedVals[k] = Integer.parseInt(valueTokens.nextToken()); 2603 k++; 2604 } 2605 2606 // Now create a set of vertical progress bar whose length is based 2607 // on the speed table value (half height) and add them to the window 2608 for (int j = 0; j < 28; j++) { 2609 JProgressBar printerBar = new JProgressBar(JProgressBar.VERTICAL, 0, 127); 2610 printerBar.setBounds(52 + j * 15, 19, 10, 127); 2611 printerBar.setValue(speedVals[j] / 2); 2612 printerBar.setBackground(Color.white); 2613 printerBar.setForeground(Color.darkGray); 2614 printerBar.setBorder(BorderFactory.createLineBorder(Color.black)); 2615 speedWindow.getContentPane().add(printerBar); 2616 // create a set of value labels at the top containing the speed table values 2617 JLabel barValLabel = new JLabel(Integer.toString(speedVals[j]), SwingConstants.CENTER); 2618 barValLabel.setBounds(50 + j * 15, 4, 15, 15); 2619 barValLabel.setFont(new Font("Monospaced", Font.PLAIN, 7)); 2620 speedWindow.getContentPane().add(barValLabel); 2621 //Create a set of labels at the bottom with the CV numbers in them 2622 JLabel barCvLabel = new JLabel(Integer.toString(67 + j), SwingConstants.CENTER); 2623 barCvLabel.setBounds(50 + j * 15, 150, 15, 15); 2624 barCvLabel.setFont(new Font("Monospaced", Font.PLAIN, 7)); 2625 speedWindow.getContentPane().add(barCvLabel); 2626 } 2627 JLabel cvLabel = new JLabel(Bundle.getMessage("Value")); 2628 cvLabel.setFont(new Font("Monospaced", Font.PLAIN, 7)); 2629 cvLabel.setBounds(25, 4, 26, 15); 2630 speedWindow.getContentPane().add(cvLabel); 2631 JLabel valueLabel = new JLabel("CV"); // I18N seems undesirable for support 2632 valueLabel.setFont(new Font("Monospaced", Font.PLAIN, 7)); 2633 valueLabel.setBounds(37, 150, 13, 15); 2634 speedWindow.getContentPane().add(valueLabel); 2635 // pass the complete window to the printing class 2636 w.write(speedWindow); 2637 // Now need to write the borders on sides of table 2638 for (int j = 0; j < speedFrameLineHeight; j++) { 2639 w.writeBorders(); 2640 w.write(s, 0, s.length()); 2641 } 2642 } 2643 } 2644 2645 final int TABLE_COLS = 3; 2646 2647 // index over CVs 2648 if (!cvList.isEmpty()) { 2649// Check how many Cvs there are to print 2650 int cvCount = cvList.size(); 2651 w.setFont(null, Font.BOLD, null); //set font to Bold 2652 // print a simple heading with I18N 2653 s = String.format("%1$21s", Bundle.getMessage("Value")) 2654 + String.format("%1$28s", Bundle.getMessage("Value")) + 2655 String.format("%1$28s", Bundle.getMessage("Value")); 2656 w.write(s, 0, s.length()); 2657 w.writeBorders(); 2658 s = "\n"; 2659 w.write(s, 0, s.length()); 2660 // NO I18N 2661 s = " CV Dec Hex CV Dec Hex CV Dec Hex"; 2662 w.write(s, 0, s.length()); 2663 w.writeBorders(); 2664 s = "\n"; 2665 w.write(s, 0, s.length()); 2666 w.setFont(null, Font.PLAIN, null); //set font back to Normal 2667 // } 2668 /*create an array to hold CV/Value strings to allow reformatting and sorting 2669 Same size as the table drawn above (TABLE_COLS columns*tableHeight; heading rows 2670 not included). Use the count of how many CVs there are to determine the number 2671 of table rows required. Add one more row if the divison into TABLE_COLS columns 2672 isn't even. 2673 */ 2674 int tableHeight = cvCount / TABLE_COLS; 2675 if (cvCount % TABLE_COLS > 0) { 2676 tableHeight++; 2677 } 2678 String[] cvStrings = new String[TABLE_COLS * tableHeight]; 2679 2680 //blank the array 2681 Arrays.fill(cvStrings, ""); 2682 2683 // get each CV and value 2684 int i = 0; 2685 for (int cvNum : cvList) { 2686 CvValue cv = _cvModel.getCvByRow(cvNum); 2687 2688 int value = cv.getValue(); 2689 2690 //convert and pad numbers as needed 2691 String numString = String.format("%12s", cv.number()); 2692 StringBuilder valueString = new StringBuilder(Integer.toString(value)); 2693 String valueStringHex = Integer.toHexString(value).toUpperCase(Locale.ENGLISH); 2694 if (value < 16) { 2695 valueStringHex = "0" + valueStringHex; 2696 } 2697 for (int j = 1; j < 3; j++) { 2698 if (valueString.length() < 3) { 2699 valueString.insert(0, " "); 2700 } 2701 } 2702 //Create composite string of CV and its decimal and hex values 2703 s = " " + numString + " " + valueString + " " + valueStringHex 2704 + " "; 2705 2706 //populate printing array - still treated as a single column 2707 cvStrings[i] = s; 2708 i++; 2709 } 2710 2711 //sort the array in CV order (just the members with values) 2712 String temp; 2713 boolean swap; 2714 do { 2715 swap = false; 2716 for (i = 0; i < _cvModel.getRowCount() - 1; i++) { 2717 if (PrintCvAction.cvSortOrderVal(cvStrings[i + 1].substring(0, 15).trim()) < PrintCvAction.cvSortOrderVal(cvStrings[i].substring(0, 15).trim())) { 2718 temp = cvStrings[i + 1]; 2719 cvStrings[i + 1] = cvStrings[i]; 2720 cvStrings[i] = temp; 2721 swap = true; 2722 } 2723 } 2724 } while (swap); 2725 2726 //Print the array in four columns 2727 for (i = 0; i < tableHeight; i++) { 2728 s = cvStrings[i] + " " + cvStrings[i + tableHeight] + " " + cvStrings[i 2729 + tableHeight * 2]; 2730 w.write(s, 0, s.length()); 2731 w.writeBorders(); 2732 s = "\n"; 2733 w.write(s, 0, s.length()); 2734 } 2735 } 2736 s = "\n"; 2737 w.writeBorders(); 2738 w.write(s, 0, s.length()); 2739 w.writeBorders(); 2740 w.write(s, 0, s.length()); 2741 2742 // handle special cases 2743 } catch (IOException e) { 2744 log.warn("error during printing", e); 2745 } 2746 2747 } 2748 2749 private JPanel addDccAddressPanel(Element e) { 2750 JPanel l = new DccAddressPanel(_varModel); 2751 panelList.add(l); 2752 // make sure this will get read/written, even if real vars not on pane 2753 int iVar; 2754 2755 // note we want Short Address first, as it might change others 2756 iVar = _varModel.findVarIndex("Short Address"); 2757 if (iVar >= 0) { 2758 varList.add(iVar); 2759 } else { 2760 log.debug("addDccAddressPanel did not find Short Address"); 2761 } 2762 2763 iVar = _varModel.findVarIndex("Address Format"); 2764 if (iVar >= 0) { 2765 varList.add(iVar); 2766 } else { 2767 log.debug("addDccAddressPanel did not find Address Format"); 2768 } 2769 2770 iVar = _varModel.findVarIndex("Long Address"); 2771 if (iVar >= 0) { 2772 varList.add(iVar); 2773 } else { 2774 log.debug("addDccAddressPanel did not find Long Address"); 2775 } 2776 2777 // included here because CV1 can modify it, even if it doesn't show on pane; 2778 iVar = _varModel.findVarIndex("Consist Address"); 2779 if (iVar >= 0) { 2780 varList.add(iVar); 2781 } else { 2782 log.debug("addDccAddressPanel did not find CV19 Consist Address"); 2783 } 2784 2785 return l; 2786 } 2787 2788 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PaneProgPane.class); 2789 2790}