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