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