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